10494 - If We Were a Child Again大总结*****

大整数除法运算详解
本文详细介绍了如何实现大整数除法运算,并通过一个具体的C++类`BigNumber`来实现大整数的基本算术操作,包括加、减、乘、除等。文章重点讨论了除法实现中的一些关键细节,如处理前导零、逐位相除等。


大整数问题之终结篇,除法运算最为复杂

主要操作:

①字符数组转置,头部存储个位,这样可使操作更为方便一些

②头置零的处理,但需要保证单个零的完整输出

③字符和整数,切勿混淆‘0’和0

④估算最后结果大致为多少位,定义MAXN


//未通过


#include <iostream> #include <string> int const MAXN=1000; using namespace std; class BigNumber { public: BigNumber(); BigNumber(int num); BigNumber(const char *num); BigNumber operator=(const char *num); BigNumber operator=(int num); BigNumber operator+(const BigNumber &c)const; BigNumber operator-(const BigNumber &c)const; BigNumber operator*(const BigNumber &c)const; BigNumber operator/(const BigNumber &c)const; BigNumber operator%(const BigNumber &c)const; BigNumber operator+=(const BigNumber &c) { *this=*this+c; return *this; } BigNumber operator-=(const BigNumber &c) { *this=*this-c; return *this; } BigNumber operator*=(const BigNumber &c) { *this=*this*c; return *this; } BigNumber operator/=(const BigNumber &c) { *this=*this/c; return *this; } BigNumber operator%=(const BigNumber &c) { *this=*this%c; return *this; } bool operator<(const BigNumber &c)const; bool operator>(const BigNumber &c)const { return c<*this; } bool operator<=(const BigNumber &c)const { return !(*this>c); } bool operator>=(const BigNumber &c)const { return !(*this<c); } bool operator==(const BigNumber &c)const { return (*this<=c)&&(*this>=c); } bool operator!=(const BigNumber &c)const { return !(*this<c)&&!(*this>c); } void cleanLeadZero(); //void divisionTen(int n); //void mutiplyTen(int n); string str()const; BigNumber getSub(int n)const; friend istream & operator>>(istream &in,BigNumber &c); friend ostream & operator<<(ostream &in,BigNumber &c); private: int s[MAXN]; int len; }; BigNumber::BigNumber() { memset(s,0,sizeof(s)); len=1;//这样设置使初始化的值为零 } BigNumber::BigNumber(const char *num) { *this=num; } BigNumber::BigNumber(int num) { *this=num; } string BigNumber::str()const { string r=""; for(int i=0;i<len;i++) r=(char)(s[i]+'0')+r; if(r=="") r="0"; return r; } BigNumber BigNumber::operator=(const char *num) { len=strlen(num); for(int i=0;i<len;i++) s[i]=num[len-1-i]-'0'; return *this; } BigNumber BigNumber::operator=(int num) { char r[MAXN]; sprintf(r,"%d",num); *this=r; return *this; } void BigNumber::cleanLeadZero() { while(len>1 && !s[len-1])//此处注意len>1,保证单个0的输出 len--; } BigNumber BigNumber::operator+(const BigNumber &c)const { BigNumber r; r.len=0; int up=0; int maxlen=len>c.len?len:c.len; for(int i=0;up || i<maxlen;i++) { int temp=up; if(i<len) temp+=s[i];//###,这里需要判断的是i<len,而不是s[i]是否为零 if(i<c.len) temp+=c.s[i]; r.s[r.len++]=temp%10; up=temp/10; } r.cleanLeadZero(); return r; } //a-b时要确保,a>b BigNumber BigNumber::operator-(const BigNumber &c)const { BigNumber r; r.len=0; int down=0; for(int i=0;i<len;i++) { int temp=s[i]-down; if(i<c.len) temp-=c.s[i];//这里是-; if(temp>=0) down=0; else { down=1; temp+=10; } r.s[r.len++]=temp; } r.cleanLeadZero(); return r; } BigNumber BigNumber::operator*(const BigNumber &c)const//大整数问题的另外一种操作,不需要中间变量 { BigNumber r; r.len=len+c.len; for(int i=0;i<len;i++) { for(int j=0;j<c.len;j++) { r.s[i+j]+=s[i]*c.s[j];//###,+= } } for(int i=0;i<r.len-1;i++) { r.s[i+1]+=r.s[i]/10; r.s[i]%=10; } r.cleanLeadZero(); return r; } BigNumber BigNumber::getSub(int n)const //###“BigNumber::getSub”: 不能将“this”指针从“const BigNumber”转换为“BigNumber &” //出现这个错误后,将函数设置成const即可 { BigNumber r; r.len=0; for(int i=0;i<n;i++) { r.s[r.len++]=s[len-n+i]; } return r; } BigNumber BigNumber::operator/(const BigNumber &c)const { BigNumber r; r.len=0; BigNumber temp=getSub(c.len-1); for(int i=len-c.len;i>=0;i--) { temp=temp*10+s[i]; if(temp<c) r.s[r.len++]=0; else { int j; for(j=1;j<=10;j++)//这里最大值可以取到10 if(c*j>temp)//c要放到*的前面,这样才可以调用operator*函数 break; r.s[r.len++]=j-1; temp=temp-(c*(j-1)); } } for(int i=0;i<r.len/2;i++) { char mid=r.s[i]; r.s[i]=r.s[r.len-1-i]; r.s[r.len-1-i]=mid; } r.cleanLeadZero(); return r; } BigNumber BigNumber::operator%(const BigNumber &c)const { BigNumber r; r=*this/c; r=*this-r*c; return r; } bool BigNumber::operator<(const BigNumber &c)const { if(len!=c.len) return len<c.len; else { for(int i=len-1;i>=0;i--)//一失足成千古恨,i-- if(s[i]!=c.s[i]) return s[i]<c.s[i]; } return false; } istream & operator>>(istream &in,BigNumber &c) { string str; in>>str; c=str.c_str(); return in; } ostream & operator<<(ostream &out,BigNumber &c) { out<<c.str(); return out; } char a[1000]; char b[100]; char op; int main() { while(scanf("%s %c %s",a,&op,b)==3) { BigNumber f1(a); BigNumber f2(b); if(op=='/') cout<<f1/f2<<endl; else if(op=='%') cout<<f1%f2<<endl; } return 0; }

<?php defined('ABSPATH') || exit; /** * Robust partial checkout to regular /checkout/ with durable cart snapshot * - Snapshot full cart before virtualizing selection for checkout * - Do not remove anything until order is created * - On success (thank-you), rebuild cart as (snapshot - purchased) * - On cancel/back (visit cart), restore snapshot * - Guest resilience: localStorage + rehydrate AJAX */ /* ------------------------------------------------- * Helpers * ------------------------------------------------- */ /** * 新增:购物车数量AJAX端点 */ add_action('wp_ajax_get_cart_count', 'pc_get_cart_count'); add_action('wp_ajax_nopriv_get_cart_count', 'pc_get_cart_count'); function pc_get_cart_count() { check_ajax_referer('woocommerce-cart', 'security'); $count = WC()->cart->get_cart_contents_count(); wp_send_json_success(array('count' => $count)); } function pc_get_cart_uid() { if (is_user_logged_in()) { return 'user_' . get_current_user_id(); } if (empty($_COOKIE['pc_cart_uid'])) { $token = wp_generate_uuid4(); setcookie('pc_cart_uid', $token, time() + YEAR_IN_SECONDS, COOKIEPATH ?: '/', '', is_ssl(), false); $_COOKIE['pc_cart_uid'] = $token; } return 'guest_' . sanitize_text_field(wp_unslash($_COOKIE['pc_cart_uid'])); } function pc_build_item_key($product_id, $variation_id = 0) { return (int)$product_id . '|' . (int)$variation_id; } function pc_snapshot_current_cart() { if (!WC()->cart) wc_load_cart(); $items = array(); foreach (WC()->cart->get_cart() as $ci_key => $ci) { $pid = isset($ci['product_id']) ? (int)$ci['product_id'] : 0; $vid = isset($ci['variation_id']) ? (int)$ci['variation_id'] : 0; $qty = isset($ci['quantity']) ? wc_stock_amount($ci['quantity']) : 0; $var = isset($ci['variation']) && is_array($ci['variation']) ? $ci['variation'] : array(); if ($pid && $qty > 0) { $items[] = array( 'product_id' => $pid, 'variation_id' => $vid, 'variation' => array_map('wc_clean', $var), 'quantity' => $qty, ); } } return $items; } function pc_restore_cart_from_items($items) { if (!WC()->cart) wc_load_cart(); WC()->cart->empty_cart(); foreach ((array)$items as $it) { $pid = isset($it['product_id']) ? (int)$it['product_id'] : 0; $vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0; $qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0; $var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array(); if ($pid && $qty > 0) { WC()->cart->add_to_cart($pid, $qty, $vid, $var); } } WC()->cart->calculate_totals(); } // Fixed: Check WC() and session existence before method_exists add_action('wp_loaded', function() { if (!function_exists('WC') || !WC() || !property_exists(WC(), 'session')) { return; } if (!method_exists(WC()->session, 'get')) return; $token = WC()->session->get('pc_partial_token'); if ($token && WC()->cart->is_empty()) { $payload = get_transient(pc_transient_key($token)); if (!empty($payload['snapshot'])) { pc_restore_cart_from_items($payload['snapshot']); WC()->cart->calculate_totals(); } } }, 20); function pc_transient_key($token) { return 'pc_partial_payload_' . sanitize_key($token); } /* ------------------------------------------------- * AJAX: Local rehydrate when Woo cart is empty * ------------------------------------------------- */ add_action('wp_ajax_pc_rehydrate_cart', 'pc_rehydrate_cart'); add_action('wp_ajax_nopriv_pc_rehydrate_cart', 'pc_rehydrate_cart'); function pc_rehydrate_cart() { check_ajax_referer('woocommerce-cart', 'security'); $raw = isset($_POST['items']) ? wp_unslash($_POST['items']) : ''; $items = is_string($raw) ? json_decode($raw, true) : (array)$raw; if (!is_array($items)) { wp_send_json_error(array('message' => 'Invalid items.'), 400); } if (!WC()->cart) wc_load_cart(); if (!WC()->cart->is_empty()) { wp_send_json_success(array('message' => 'Cart not empty.')); } foreach ($items as $it) { $pid = isset($it['product_id']) ? (int)$it['product_id'] : 0; $vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0; $qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0; $var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array(); if ($pid && $qty > 0) { WC()->cart->add_to_cart($pid, $qty, $vid, $var); } } WC()->cart->calculate_totals(); wp_send_json_success(array('rehydrated' => true)); } /* ------------------------------------------------- * AJAX: Update qty (per-row; no page reload) * ------------------------------------------------- */ add_action('wp_ajax_update_cart_item_qty', 'pc_update_cart_item_qty'); add_action('wp_ajax_nopriv_update_cart_item_qty', 'pc_update_cart_item_qty'); function pc_update_cart_item_qty() { check_ajax_referer('woocommerce-cart', 'security'); $key = isset($_POST['cart_item_key']) ? wc_clean(wp_unslash($_POST['cart_item_key'])) : ''; $qty = isset($_POST['qty']) ? wc_stock_amount($_POST['qty']) : null; if (!$key || $qty === null) { wp_send_json_error(array('message' => 'Missing params.'), 400); } if (!WC()->cart) wc_load_cart(); if ($qty <= 0) { $removed = WC()->cart->remove_cart_item($key); WC()->cart->calculate_totals(); wp_send_json_success(array('removed' => (bool)$removed)); } else { $set = WC()->cart->set_quantity($key, $qty, true); WC()->cart->calculate_totals(); $cart_item = WC()->cart->get_cart_item($key); if (!$cart_item) { wp_send_json_error(array('message' => 'Cart item not found after update.'), 404); } $_product = $cart_item['data']; $subtotal_html = apply_filters( 'woocommerce_cart_item_subtotal', WC()->cart->get_product_subtotal($_product, $cart_item['quantity']), $cart_item, $key ); // Use line_total + line_tax (after totals) for checkbox data-price $line_total_incl_tax = (float)($cart_item['line_total'] + $cart_item['line_tax']); wp_send_json_success(array( 'subtotal_html' => $subtotal_html, 'line_total_incl_tax' => $line_total_incl_tax, 'removed' => false, )); } } /* ------------------------------------------------- * AJAX: Remove selected * ------------------------------------------------- */ add_action('wp_ajax_remove_selected_cart_items', 'pc_remove_selected_cart_items'); add_action('wp_ajax_nopriv_remove_selected_cart_items', 'pc_remove_selected_cart_items'); function pc_remove_selected_cart_items() { check_ajax_referer('woocommerce-cart', 'security'); $keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array(); if (!WC()->cart) wc_load_cart(); foreach ($keys as $k) { $k = wc_clean(wp_unslash($k)); WC()->cart->remove_cart_item($k); } WC()->cart->calculate_totals(); wp_send_json_success(true); } /* ------------------------------------------------- * AJAX: Empty cart * ------------------------------------------------- */ add_action('wp_ajax_empty_cart', 'pc_empty_cart'); add_action('wp_ajax_nopriv_empty_cart', 'pc_empty_cart'); function pc_empty_cart() { check_ajax_referer('woocommerce-cart', 'security'); if (!WC()->cart) wc_load_cart(); WC()->cart->empty_cart(); wp_send_json_success(true); } /* ------------------------------------------------- * AJAX: Apply coupon * ------------------------------------------------- */ add_action('wp_ajax_apply_coupon', 'pc_apply_coupon'); add_action('wp_ajax_nopriv_apply_coupon', 'pc_apply_coupon'); function pc_apply_coupon() { check_ajax_referer('woocommerce-cart', 'security'); $code = isset($_POST['coupon_code']) ? wc_format_coupon_code(wp_unslash($_POST['coupon_code'])) : ''; if (!$code) { wp_send_json_error(array('message' => __('请输入优惠券代码', 'woocommerce')), 400); } if (!WC()->cart) wc_load_cart(); $applied = WC()->cart->apply_coupon($code); WC()->cart->calculate_totals(); if (is_wp_error($applied)) { wp_send_json_error(array('message' => $applied->get_error_message()), 400); } if (!$applied) { wp_send_json_error(array('message' => __('优惠券应用失败', 'woocommerce')), 400); } wp_send_json_success(true); } /* ------------------------------------------------- * AJAX: Start partial checkout to regular checkout page * ------------------------------------------------- */ add_action('wp_ajax_create_direct_order', 'pc_create_direct_order'); add_action('wp_ajax_nopriv_create_direct_order', 'pc_create_direct_order'); function pc_create_direct_order() { check_ajax_referer('woocommerce-cart', 'security'); $selected_keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array(); if (empty($selected_keys)) { wp_send_json_error(array('message' => __('请选择要结算的商品', 'woocommerce')), 400); } if (!WC()->cart) wc_load_cart(); // Snapshot full cart $snapshot = pc_snapshot_current_cart(); // Build selected items from current cart based on cart_item_key list $selected = array(); foreach (WC()->cart->get_cart() as $ci_key => $ci) { if (!in_array($ci_key, $selected_keys, true)) { continue; } $pid = (int)$ci['product_id']; $vid = (int)$ci['variation_id']; $qty = wc_stock_amount($ci['quantity']); $var = isset($ci['variation']) && is_array($ci['variation']) ? array_map('wc_clean', $ci['variation']) : array(); if ($pid && $qty > 0) { $selected[] = array( 'product_id' => $pid, 'variation_id' => $vid, 'variation' => $var, 'quantity' => $qty, ); } } if (empty($selected)) { wp_send_json_error(array('message' => __('没有可结算的商品', 'woocommerce')), 400); } $token = wp_generate_uuid4(); $payload = array( 'uid' => pc_get_cart_uid(), 'snapshot' => $snapshot, 'selected' => $selected, 'created' => time(), ); set_transient(pc_transient_key($token), $payload, 2 * DAY_IN_SECONDS); // Put token in session (used across checkout AJAX calls) if (method_exists(WC()->session, 'set')) { WC()->session->set('pc_partial_token', $token); } $checkout_url = add_query_arg('pc_token', rawurlencode($token), wc_get_checkout_url()); wp_send_json_success(array('checkout_url' => $checkout_url)); } /* ------------------------------------------------- * Virtualize cart on checkout for token and rebuild after purchase * ------------------------------------------------- */ // Entering checkout with token: virtualize cart to selected items add_action('woocommerce_before_checkout_form', function() { if (!isset($_GET['pc_token'])) return; $token = sanitize_text_field(wp_unslash($_GET['pc_token'])); $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['selected'])) return; if (!WC()->cart) wc_load_cart(); // Virtualize to selected items only pc_restore_cart_from_items($payload['selected']); // Persist token in session for all subsequent checkout AJAX calls if (method_exists(WC()->session, 'set')) { WC()->session->set('pc_partial_token', $token); } }, 1); // Safety: just-in-time re-virtualization before order processing add_action('woocommerce_before_checkout_process', function() { if (!method_exists(WC()->session, 'get')) return; $token = WC()->session->get('pc_partial_token'); if (!$token) return; $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['selected'])) return; // Ensure cart still equals selected set pc_restore_cart_from_items($payload['selected']); }, 1); // Tag order with token add_action('woocommerce_checkout_create_order', function($order) { $token = null; if (isset($_GET['pc_token'])) { $token = sanitize_text_field(wp_unslash($_GET['pc_token'])); } elseif (method_exists(WC()->session, 'get')) { $token = WC()->session->get('pc_partial_token'); } if ($token) { $order->update_meta_data('_pc_partial_token', $token); $order->update_meta_data('_pc_cart_snapshot', $token); } }, 10, 1); // Fixed: Only remove purchased items from cart add_action('woocommerce_thankyou', function($order_id) { $order = wc_get_order($order_id); if (!$order) return; $token = $order->get_meta('_pc_partial_token'); if (!$token) return; $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['snapshot'])) { if (method_exists(WC()->session, 'set')) { WC()->session->set('pc_partial_token', null); } delete_transient(pc_transient_key($token)); return; } // 1. Restore FULL snapshot (all items) pc_restore_cart_from_items($payload['snapshot']); WC()->cart->calculate_totals(); // 2. Remove only the purchased (selected) items foreach ($payload['selected'] as $selected_item) { $cart_item_key = pc_find_cart_item($selected_item['product_id'], $selected_item['variation_id']); if ($cart_item_key) { WC()->cart->remove_cart_item($cart_item_key); } } // 3. Update cart totals WC()->cart->calculate_totals(); // Cleanup token if (method_exists(WC()->session, 'set')) { WC()->session->set('pc_partial_token', null); } delete_transient(pc_transient_key($token)); }, 20); // Helper: Find cart item by product and variation ID function pc_find_cart_item($product_id, $variation_id = 0) { if (!WC()->cart) wc_load_cart(); foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) { $cart_pid = isset($cart_item['product_id']) ? (int)$cart_item['product_id'] : 0; $cart_vid = isset($cart_item['variation_id']) ? (int)$cart_item['variation_id'] : 0; if ($cart_pid == $product_id && $cart_vid == $variation_id) { return $cart_item_key; } } return false; } // Visiting cart with active token: restore full snapshot (cancel/back) add_action('woocommerce_before_cart', function() { if (!method_exists(WC()->session, 'get')) return; $token = WC()->session->get('pc_partial_token'); if (!$token) return; $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['snapshot'])) return; // Restore full snapshot so cart page always shows everything pc_restore_cart_from_items($payload['snapshot']); }, 1); /* ------------------------------------------------- * Keep cart count accurate during checkout process * ------------------------------------------------- */ add_filter('woocommerce_cart_contents_count', function($count) { // Check if partial token exists if (!method_exists(WC()->session, 'get')) return $count; $token = WC()->session->get('pc_partial_token'); if ($token) { $payload = get_transient(pc_transient_key($token)); // Always show full cart count even during checkout if (!empty($payload['snapshot'])) { $snapshot_count = 0; foreach ($payload['snapshot'] as $item) { $snapshot_count += (int)$item['quantity']; } return $snapshot_count; } } return $count; }); // Ensure cart item totals are accurate in header add_action('woocommerce_before_cart', function() { pc_maintain_cart_consistency(); }); add_action('woocommerce_before_checkout_form', function() { pc_maintain_cart_consistency(); }); function pc_maintain_cart_consistency() { if (!method_exists(WC()->session, 'get')) return; $token = WC()->session->get('pc_partial_token'); if (!$token) return; $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['snapshot'])) return; // Immediately restore full cart for consistent UI pc_restore_cart_from_items($payload['snapshot']); WC()->cart->calculate_totals(); } it says Your PHP code changes were not applied due to an error on line 86 of file wp-content/themes/woodmart-child/functions.php. Please fix and try saving again. Uncaught TypeError: method_exists(): Argument #1 ($object_or_class) must be of type object|string, null given in wp-content/themes/woodmart-child/functions.php:86 Stack trace: #0 wp-content/themes/woodmart-child/functions.php(86): method_exists() #1 wp-includes/class-wp-hook.php(324): {closure}() #2 wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters() #3 wp-includes/plugin.php(517): WP_Hook->do_action() #4 wp-settings.php(749): do_action() #5 wp-config.php(105): require_once('/home/u18285596...') #6 wp-load.php(50): require_once('/home/u18285596...') #7 wp-admin/admin.php(35): require_once('/home/u18285596...') #8 wp-admin/theme-editor.php(10): require_once('/home/u18285596...') #9 {main} thrown Please regenerate the FULL CODE for me to be replaced with the current one in functions.php
09-06
on functions.php, it says Your PHP code changes were not applied due to an error on line 82 of file wp-content/themes/woodmart-child/functions.php. Please fix and try saving again. Uncaught TypeError: method_exists(): Argument #1 ($object_or_class) must be of type object|string, null given in wp-content/themes/woodmart-child/functions.php:82 Stack trace: #0 wp-content/themes/woodmart-child/functions.php(82): method_exists() #1 wp-includes/class-wp-hook.php(324): {closure}() #2 wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters() #3 wp-includes/plugin.php(517): WP_Hook->do_action() #4 wp-settings.php(749): do_action() #5 wp-config.php(105): require_once(‘/home/u18285596…’) #6 wp-load.php(50): require_once(‘/home/u18285596…’) #7 wp-admin/admin.php(35): require_once(‘/home/u18285596…’) #8 wp-admin/theme-editor.php(10): require_once(‘/home/u18285596…’) #9 {main} thrown cart.php <?php /** * Custom Cart Page for WooCommerce with Selective Checkout */ if (!defined('ABSPATH')) { exit; } do_action('woocommerce_before_cart'); // Provide context for JS (no layout change) $pc_cart_is_empty = WC()->cart->is_empty(); function pc_uid_for_view() { if (is_user_logged_in()) return 'user_' . get_current_user_id(); if (empty($_COOKIE['pc_cart_uid'])) { $token = wp_generate_uuid4(); setcookie('pc_cart_uid', $token, time() + YEAR_IN_SECONDS, COOKIEPATH ?: '/', '', is_ssl(), false); $_COOKIE['pc_cart_uid'] = $token; } return 'guest_' . sanitize_text_field(wp_unslash($_COOKIE['pc_cart_uid'])); } $pc_uid = pc_uid_for_view(); // 已修正:仅在令牌存在时恢复购物车 if (method_exists(WC()->session, 'get')) { $token = WC()->session->get('pc_partial_token'); if ($token && !WC()->cart->is_empty()) { $payload = get_transient(pc_transient_key($token)); if (!empty($payload['snapshot'])) { pc_restore_cart_from_items($payload['snapshot']); WC()->cart->calculate_totals(); } } } ?> <div class="cart-page-section container" style="max-width: 1200px; margin: 0 auto;"> <form class="woocommerce-cart-form" action="<?php echo esc_url( wc_get_cart_url() ); ?>" method="post"> <?php do_action('woocommerce_before_cart_table'); ?> <?php wp_nonce_field('woocommerce-cart', 'woocommerce-cart-nonce'); ?> <table class="shop_table shop_table_responsive cart woocommerce-cart-form__contents"> <thead> <tr> <th class="product-select" style="width: 8%;"> <input type="checkbox" id="select-all-items" /> <label for="select-all-items" style="display: inline-block; margin-left: 5px; cursor: pointer;">全选</label> </th> <th class="product-info"><?php esc_html_e('Product', 'woocommerce'); ?></th> <th class="product-price"><?php esc_html_e('Price', 'woocommerce'); ?></th> <th class="product-quantity"><?php esc_html_e('Quantity', 'woocommerce'); ?></th> <th class="product-subtotal"><?php esc_html_e('Subtotal', 'woocommerce'); ?></th> <th class="product-remove"><?php esc_html_e('操作', 'woocommerce'); ?></th> </tr> </thead> <tbody> <?php do_action('woocommerce_before_cart_contents'); ?> <?php foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) : ?> <?php $_product = apply_filters('woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key); $product_id = apply_filters('woocommerce_cart_item_product_id', $cart_item['product_id'], $cart_item, $cart_item_key); if ( $_product && $_product->exists() && $cart_item['quantity'] > 0 && apply_filters('woocommerce_cart_item_visible', true, $cart_item, $cart_item_key) ) : $product_permalink = apply_filters('woocommerce_cart_item_permalink', $_product->is_visible() ? $_product->get_permalink($cart_item) : '', $cart_item, $cart_item_key); $line_total_value = (float) ($cart_item['line_total'] + $cart_item['line_tax']); $variation_id = isset($cart_item['variation_id']) ? (int)$cart_item['variation_id'] : 0; $variation_data = isset($cart_item['variation']) ? $cart_item['variation'] : array(); $variation_json = wp_json_encode($variation_data); ?> <tr class="woocommerce-cart-form__cart-item <?php echo esc_attr( apply_filters('woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key) ); ?>" data-cart_item_key="<?php echo esc_attr($cart_item_key); ?>" data-product_id="<?php echo esc_attr($product_id); ?>" data-variation_id="<?php echo esc_attr($variation_id); ?>" data-variation="<?php echo esc_attr($variation_json); ?>"> <!-- Checkbox Column --> <td class="product-select" data-title="<?php esc_attr_e('Select', 'woocommerce'); ?>"> <input type="checkbox" class="item-checkbox" name="selected_items[]" value="<?php echo esc_attr($cart_item_key); ?>" data-price="<?php echo esc_attr($line_total_value); ?>" /> </td> <!-- Product Info Column --> <td class="product-info" data-title="<?php esc_attr_e('Product', 'woocommerce'); ?>"> <div class="product-image"> <?php $thumbnail = apply_filters('woocommerce_cart_item_thumbnail', $_product->get_image(), $cart_item, $cart_item_key); if ( ! $product_permalink ) : echo $thumbnail; // PHPCS: XSS ok. else : printf('<a href="%s">%s</a>', esc_url($product_permalink), $thumbnail); // PHPCS: XSS ok. endif; ?> </div> <div class="product-name"> <?php if ( ! $product_permalink ) : echo wp_kses_post( apply_filters('woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key) . ' ' ); else : echo wp_kses_post( apply_filters('woocommerce_cart_item_name', sprintf('<a href="%s">%s</a>', esc_url($product_permalink), $_product->get_name()), $cart_item, $cart_item_key) ); endif; do_action('woocommerce_after_cart_item_name', $cart_item, $cart_item_key); echo wc_get_formatted_cart_item_data($cart_item); // PHPCS: XSS ok. ?> </div> </td> <!-- Price Column --> <td class="product-price" data-title="<?php esc_attr_e('Price', 'woocommerce'); ?>"> <?php echo apply_filters('woocommerce_cart_item_price', WC()->cart->get_product_price($_product), $cart_item, $cart_item_key); // PHPCS: XSS ok. ?> </td> <!-- Quantity Column --> <td class="product-quantity" data-title="<?php esc_attr_e('Quantity', 'woocommerce'); ?>"> <?php if ( $_product->is_sold_individually() ) : $product_quantity = sprintf('1 <input type="hidden" name="cart[%s][qty]" value="1" />', $cart_item_key); else : $product_quantity = woocommerce_quantity_input( array( 'input_name' => "cart[{$cart_item_key}][qty]", 'input_value' => $cart_item['quantity'], 'max_value' => $_product->get_max_purchase_quantity(), 'min_value' => '0', 'product_name' => $_product->get_name(), ), $_product, false ); endif; echo apply_filters('woocommerce_cart_item_quantity', $product_quantity, $cart_item_key, $cart_item); // PHPCS: XSS ok. ?> <small class="qty-status" style="display:none;margin-left:8px;color:#666;">保存中…</small> </td> <!-- Subtotal Column --> <td class="product-subtotal" data-title="<?php esc_attr_e('Subtotal', 'woocommerce'); ?>"> <?php echo apply_filters('woocommerce_cart_item_subtotal', WC()->cart->get_product_subtotal($_product, $cart_item['quantity']), $cart_item, $cart_item_key); // PHPCS: XSS ok. ?> </td> <!-- Remove Item Column --> <td class="product-remove" data-title="<?php esc_attr_e('操作', 'woocommerce'); ?>"> <?php echo apply_filters('woocommerce_cart_item_remove_link', sprintf( '<a href="%s" class="remove" aria-label="%s" data-product_id="%s" data-product_sku="%s">×</a>', esc_url( wc_get_cart_remove_url($cart_item_key) ), esc_attr__('Remove this item', 'woocommerce'), esc_attr($product_id), esc_attr($_product->get_sku()) ), $cart_item_key); ?> </td> </tr> <?php endif; ?> <?php endforeach; ?> <?php do_action('woocommerce_after_cart_contents'); ?> </tbody> </table> <?php do_action('woocommerce_after_cart_table'); ?> </form> </div> <!-- Sticky Footer --> <div class="cart-footer-actions sticky-footer" style="position: sticky; bottom: 0; background: white; padding: 15px; border-top: 1px solid #ddd; max-width: 1200px; margin: 0 auto;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <div class="footer-left"> <input type="checkbox" id="footer-select-all"> <label for="footer-select-all" style="display: inline-block; margin-left: 5px; cursor: pointer;">全选</label> <button type="button" class="button" id="remove-selected-items">刪除選中的商品</button> <button type="button" class="button" id="clear-cart">清空購物車</button> </div> <div class="coupon-section"> <input type="text" name="coupon_code" class="input-text" id="coupon_code" value="" placeholder="输入优惠券代码" style="padding: 8px; width: 200px; border: 1px solid #ddd; border-radius: 4px; margin-right: 5px;" /> <button type="button" class="button" id="apply-coupon">应用优惠券</button> </div> </div> <div style="display: flex; justify-content: space-between; align-items: center;"> <div class="selected-summary" style="font-size: 16px; font-weight: bold;"> 已选商品: <span id="selected-count">0</span> 件,共计: <span id="selected-total">RM0.00</span> </div> <a href="<?php echo esc_url( wc_get_checkout_url() ); ?>" class="checkout-button button alt wc-forward" id="partial-checkout">结算</a> </div> </div> <?php do_action('woocommerce_after_cart'); ?> <style> /* Layout styles (unchanged) */ .cart-page-section { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); margin-bottom: 30px; } .cart-page-section table.cart { width: 100%; border-collapse: collapse; margin-bottom: 20px; } .cart-page-section table.cart th, .cart-page-section table.cart td { padding: 15px; text-align: left; border-bottom: 1px solid #eee; } .cart-page-section table.cart th { background-color: #f8f8f8; font-weight: bold; } .cart-page-section table.cart th.product-select, .cart-page-section table.cart td.product-select { width: 8%; text-align: center; vertical-align: middle; white-space: nowrap; } .cart-page-section table.cart td.product-info { width: 37%; vertical-align: top; } .cart-page-section table.cart .product-info .product-image { float: left; margin-right: 15px; width: 100px; height: 100px; } .cart-page-section table.cart .product-info .product-image img { max-width: 100%; height: auto; display: block; } .cart-page-section table.cart .product-info .product-name { overflow: hidden; } .cart-page-section table.cart td.product-price, .cart-page-section table.cart td.product-quantity, .cart-page-section table.cart td.product-subtotal { width: 15%; vertical-align: middle; } .cart-page-section table.cart td.product-remove { width: 5%; text-align: center; vertical-align: middle; } .cart-footer-actions { position: sticky; bottom: 0; background: #fff; padding: 15px; border-top: 1px solid #ddd; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); z-index: 1000; display: flex; flex-direction: column; gap: 15px; } .footer-left { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } .coupon-section { display: flex; align-items: center; gap: 5px; } .selected-summary { font-size: 16px; font-weight: bold; flex: 1; } .cart-footer-actions .button { padding: 10px 20px; background-color: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.3s; white-space: nowrap; } .cart-footer-actions .button:hover { background-color: #d32f2f; } #partial-checkout { padding: 12px 24px; background-color: #2196F3; color: white; text-decoration: none; border-radius: 4px; display: inline-block; transition: background-color 0.3s; white-space: nowrap; text-align: center; font-weight: bold; } #partial-checkout:hover { background-color: #0b7dda; } #coupon_code { padding: 8px; width: 200px; border: 1px solid #ddd; border-radius: 4px; transition: border-color 0.3s; } #coupon_code:focus { border-color: #2196F3; outline: none; } /* Responsive (unchanged) */ @media (max-width: 768px) { .cart-page-section table.cart thead { display: none; } .cart-page-section table.cart td { display: block; width: 100% !important; text-align: right; padding: 10px; position: relative; padding-left: 50%; } .cart-page-section table.cart td::before { content: attr(data-title); position: absolute; left: 15px; font-weight: bold; text-align: left; } .cart-page-section table.cart .product-info .product-image { float: none; margin: 0 auto 10px; } .cart-page-section table.cart td.product-select::before { content: "选择"; } .cart-footer-actions { flex-direction: column; align-items: flex-start; } .footer-left { width: 100%; justify-content: space-between; } .coupon-section { width: 100%; margin-top: 10px; } .coupon-section input { flex: 1; } .cart-footer-actions .footer-bottom-row { flex-direction: column; align-items: stretch; } .selected-summary { text-align: center; margin-bottom: 10px; } #partial-checkout { width: 100%; padding: 15px; } .cart-footer-actions .button { padding: 12px 15px; margin: 5px 0; width: 100%; text-align: center; } } @media (max-width: 480px) { .cart-page-section { padding: 15px; } .cart-page-section table.cart td { padding-left: 45%; } .cart-page-section table.cart td::before { font-size: 14px; } .cart-footer-actions { padding: 10px; } #coupon_code { width: 100%; } } /* Loader overlay above quantity input */ .product-quantity .quantity { position: relative; /* anchor for overlay */ } .quantity-saving-overlay { position: absolute; top: 0; left: 0; right: 0; height: 100%; display: none; /* hidden by default */ align-items: flex-start; /* appear at top */ justify-content: center;/* centered horizontally */ padding-top: 2px; background: rgba(255,255,255,0.0); /* transparent so it doesn't dim UI */ z-index: 10; pointer-events: none; /* don't block typing/clicks when visible */ } .quantity-saving-loader { width: 20px; height: 20px; border: 3px solid #f3f3f3; border-top: 3px solid #3498db; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> <script> jQuery(function($){ // Context var PC = { ajax_url: (window.wc_cart_params && window.wc_cart_params.ajax_url) || '<?php echo esc_url( admin_url("admin-ajax.php") ); ?>', security: (function(){ var n = $('input[name="woocommerce-cart-nonce"]').val(); if (n) return n; if (window.wc_cart_params && window.wc_cart_params.cart_nonce) return window.wc_cart_params.cart_nonce; return '<?php echo wp_create_nonce("woocommerce-cart"); ?>'; })(), cart_is_empty: <?php echo $pc_cart_is_empty ? 'true' : 'false'; ?>, cart_uid: '<?php echo esc_js($pc_uid); ?>' }; // Keys function lsKey(name){ return 'pc_' + name + '_' + PC.cart_uid; } var LS_SELECTION = lsKey('selected_items'); var LS_MASTER = lsKey('cart_master'); // Utilities function getSelectedKeys(){ return $('.item-checkbox:checked').map(function(){ return this.value; }).get(); } function fmtRM(n){ n = isNaN(n) ? 0 : n; return 'RM' + Number(n).toFixed(2); } // Selection persistence function readSelection(){ try { return JSON.parse(localStorage.getItem(LS_SELECTION) || '[]'); } catch(e){ return []; } } function writeSelection(keys){ try { localStorage.setItem(LS_SELECTION, JSON.stringify(keys||[])); } catch(e){} } function restoreSelectionFromLS(){ var saved = readSelection(); if (!Array.isArray(saved)) saved = []; $('.item-checkbox').each(function(){ $(this).prop('checked', saved.indexOf(this.value) !== -1); }); } window.addEventListener('storage', function(ev){ if (ev.key === LS_SELECTION) { restoreSelectionFromLS(); updateSelectedSummary(); } }); // Selected summary function updateSelectedSummary(){ var total = 0, count = 0; $('.item-checkbox:checked').each(function(){ var price = parseFloat($(this).data('price')); if (!isNaN(price)) { total += price; count++; } }); $('#selected-count').text(count); $('#selected-total').text(fmtRM(total)); var totalCbs = $('.item-checkbox').length; var checkedCbs = $('.item-checkbox:checked').length; var allChecked = totalCbs > 0 && checkedCbs === totalCbs; $('#select-all-items, #footer-select-all').prop('checked', allChecked); writeSelection(getSelectedKeys()); } // Select all $('#select-all-items, #footer-select-all').off('change.sc').on('change.sc', function(){ var checked = $(this).prop('checked'); $('.item-checkbox').prop('checked', checked); updateSelectedSummary(); }); // 单个勾选项变化时,更新统计与“全选”状态 $(document).on('change', '.item-checkbox', updateSelectedSummary); // Snapshot cart DOM -> localStorage (guest resilience) function snapshotCartFromDOM() { var items = []; $('tr.cart_item').each(function(){ var $row = $(this); var pid = parseInt($row.attr('data-product_id'),10)||0; var vid = parseInt($row.attr('data-variation_id'),10)||0; var qty = parseFloat($row.find('input.qty').val())||0; var variation = {}; try { variation = JSON.parse($row.attr('data-variation')||'{}'); } catch(e){ variation = {}; } if (pid && qty > 0) { items.push({ product_id: pid, variation_id: vid, variation: variation, quantity: qty }); } }); try { localStorage.setItem(LS_MASTER, JSON.stringify({ ts: Date.now(), items: items })); } catch(e){} } function maybeRehydrateFromLocal() { if (!PC.cart_is_empty) return; var raw = localStorage.getItem(LS_MASTER); if (!raw) return; var data; try { data = JSON.parse(raw); } catch(e){ return; } var items = (data && data.items) ? data.items : []; if (!items.length) return; $.ajax({ url: PC.ajax_url, method: 'POST', dataType: 'json', data: { action: 'pc_rehydrate_cart', security: PC.security, items: JSON.stringify(items) } }).done(function(res){ if (res && res.success) { location.reload(); // display rehydrated items } }); } // AFTER $('#remove-selected-items').off('click.sc').on('click.sc', function(){ var keys = getSelectedKeys(); if (!keys.length) { alert('请选择要删除的商品'); return; } if (!confirm('确定要删除选中的商品吗?')) return; $.ajax({ url: PC.ajax_url, method: 'POST', dataType: 'json', data: { action: 'remove_selected_cart_items', selected_items: keys, security: PC.security } }).done(function(res){ if (res && res.success) { keys.forEach(function(k){ var $row = $('tr.cart_item[data-cart_item_key="'+k+'"]'); $row.fadeOut(250, function(){ $(this).remove(); snapshotCartFromDOM(); updateSelectedSummary(); }); }); // 从本地选择集合中剔除已删除项 var saved = readSelection().filter(function(k0){ return keys.indexOf(k0) === -1; }); writeSelection(saved); } else { alert((res && res.data && (res.data.message || res.data)) || '删除失败,请重试'); } }).fail(function(){ alert('删除失败,请重试'); }); }); // Clear cart // AFTER $('#clear-cart').off('click.sc').on('click.sc', function(){ if (!confirm('确定要清空购物车吗?')) return; $.ajax({ url: PC.ajax_url, method: 'POST', dataType: 'json', data: { action: 'empty_cart', security: PC.security } }).done(function(res){ if (res && res.success) { writeSelection([]); localStorage.removeItem(LS_MASTER); location.reload(); } else { alert((res && res.data && (res.data.message || res.data)) || '清空购物车失败,请重试'); } }).fail(function(){ alert('清空购物车失败,请重试'); }); }); // Apply coupon // AFTER $('#apply-coupon').off('click.sc').on('click.sc', function(){ var code = ($.trim($('#coupon_code').val()) || ''); if (!code) { alert('请输入优惠券代码'); return; } var $btn = $(this).prop('disabled', true).text('处理中...'); $.ajax({ url: PC.ajax_url, method: 'POST', dataType: 'json', data: { action: 'apply_coupon', coupon_code: code, security: PC.security } }).done(function(res){ if (res && res.success) { location.reload(); } else { alert((res && res.data && (res.data.message || res.data)) || '优惠券应用失败,请重试'); $btn.prop('disabled', false).text('应用优惠券'); } }).fail(function(){ alert('发生错误,请重试'); $btn.prop('disabled', false).text('应用优惠券'); }); }); // ---- Quantity auto-save (fixed regex + loader above input) ---- // 已修正:正确的正则表达式 function parseCartKeyFromInputName(n) { var m = (n || '').match(/^cart\[([a-zA-Z0-9_]+)\]\[qty\]$/); return m ? m[1] : null; } function ensureLoader($quantityContainer){ var $overlay = $quantityContainer.find('.quantity-saving-overlay'); if (!$overlay.length) { $overlay = $('<div class="quantity-saving-overlay"><div class="quantity-saving-loader"></div></div>'); $quantityContainer.append($overlay); } return $overlay; } var qtyTimers = {}; $(document).on('input change', 'input.qty', function(){ var $input = $(this); var key = parseCartKeyFromInputName($input.attr('name')); if (!key) return; var $row = $input.closest('tr.cart_item'); var $quantityContainer = $input.closest('.quantity'); var val = parseInt($input.val(), 10); if (isNaN(val) || val < 0) val = 0; // debounce if (qtyTimers[key]) clearTimeout(qtyTimers[key]); var $overlay = ensureLoader($quantityContainer); $overlay.hide(); qtyTimers[key] = setTimeout(function(){ $overlay.show(); $input.prop('disabled', true); $.ajax({ url: PC.ajax_url, method: 'POST', dataType: 'json', data: { action: 'update_cart_item_qty', cart_item_key: key, qty: val, security: PC.security } }).done(function(res){ if (res && res.success && res.data){ if (res.data.subtotal_html) { $row.find('td.product-subtotal').html(res.data.subtotal_html); } var $cb = $row.find('.item-checkbox'); if ($cb.length && typeof res.data.line_total_incl_tax === 'number') { $cb.attr('data-price', res.data.line_total_incl_tax); } if (val === 0 || res.data.removed) { $row.fadeOut(300, function(){ $(this).remove(); snapshotCartFromDOM(); updateSelectedSummary(); }); } else { snapshotCartFromDOM(); updateSelectedSummary(); } } else { var msg = (res && res.data && (res.data.message || res.data)) || '数量更新失败'; alert(msg); } }).fail(function(){ alert('数量更新失败,请重试'); }).always(function(){ $overlay.hide(); $input.prop('disabled', false); }); }, 500); // 0.5s debounce }); // Partial checkout -> regular checkout page $('#partial-checkout').off('click.sc').on('click.sc', function(e){ e.preventDefault(); var keys = getSelectedKeys(); if (!keys.length) { alert('请至少选择一件商品结算'); return; } var $btn = $(this), t = $btn.text(); $btn.prop('disabled', true).text('创建订单中...'); snapshotCartFromDOM(); $.ajax({ url: PC.ajax_url, method: 'POST', dataType: 'json', data: { action: 'create_direct_order', selected_items: keys, security: PC.security } }).done(function(res){ if (res && res.success && res.data && res.data.checkout_url) { window.location.href = res.data.checkout_url; // /checkout/?pc_token=... } else { alert((res && res.data && (res.data.message || res.data)) || '创建订单失败,请重试'); $btn.prop('disabled', false).text(t); } }).fail(function(xhr){ var msg = '创建订单失败'; if (xhr && xhr.responseJSON && xhr.responseJSON.data) { msg += ':' + (xhr.responseJSON.data.message || xhr.responseJSON.data); } alert(msg); $btn.prop('disabled', false).text(t); }); }); // Keep LS selection after clicking "x" remove; also snapshot $(document).on('click', 'a.remove', function(){ var key = $(this).closest('tr.cart_item').attr('data-cart_item_key'); if (key) { var saved = readSelection().filter(function(k){ return k !== key; }); writeSelection(saved); snapshotCartFromDOM(); } }); // Init restoreSelectionFromLS(); updateSelectedSummary(); snapshotCartFromDOM(); maybeRehydrateFromLocal(); }); // NEW: Real-time cart count updater function updateHeaderCartCount() { $.ajax({ url: PC.ajax_url, method: 'POST', data: { action: 'get_cart_count', security: PC.security }, success: function(response) { if (response.success) { $('.cart-count').text(response.count); } } }); } // Update on cart changes $(document).on('cart_updated', function() { updateHeaderCartCount(); }); // Initial update updateHeaderCartCount(); }); </script> functions.php <?php defined('ABSPATH') || exit; /** * Robust partial checkout to regular /checkout/ with durable cart snapshot * - Snapshot full cart before virtualizing selection for checkout * - Do not remove anything until order is created * - On success (thank-you), rebuild cart as (snapshot - purchased) * - On cancel/back (visit cart), restore snapshot * - Guest resilience: localStorage + rehydrate AJAX */ /* ------------------------------------------------- * Helpers * ------------------------------------------------- */ /** * 新增:购物车数量AJAX端点 */ add_action('wp_ajax_get_cart_count', 'pc_get_cart_count'); add_action('wp_ajax_nopriv_get_cart_count', 'pc_get_cart_count'); function pc_get_cart_count() { check_ajax_referer('woocommerce-cart', 'security'); $count = WC()->cart->get_cart_contents_count(); wp_send_json_success(array('count' => $count)); } function pc_get_cart_uid() { if (is_user_logged_in()) { return 'user_' . get_current_user_id(); } if (empty($_COOKIE['pc_cart_uid'])) { $token = wp_generate_uuid4(); setcookie('pc_cart_uid', $token, time() + YEAR_IN_SECONDS, COOKIEPATH ?: '/', '', is_ssl(), false); $_COOKIE['pc_cart_uid'] = $token; } return 'guest_' . sanitize_text_field(wp_unslash($_COOKIE['pc_cart_uid'])); } function pc_build_item_key($product_id, $variation_id = 0) { return (int)$product_id . '|' . (int)$variation_id; } function pc_snapshot_current_cart() { if (!WC()->cart) wc_load_cart(); $items = array(); foreach (WC()->cart->get_cart() as $ci_key => $ci) { $pid = isset($ci['product_id']) ? (int)$ci['product_id'] : 0; $vid = isset($ci['variation_id']) ? (int)$ci['variation_id'] : 0; $qty = isset($ci['quantity']) ? wc_stock_amount($ci['quantity']) : 0; $var = isset($ci['variation']) && is_array($ci['variation']) ? $ci['variation'] : array(); if ($pid && $qty > 0) { $items[] = array( 'product_id' => $pid, 'variation_id' => $vid, 'variation' => array_map('wc_clean', $var), 'quantity' => $qty, ); } } return $items; } function pc_restore_cart_from_items($items) { if (!WC()->cart) wc_load_cart(); WC()->cart->empty_cart(); foreach ((array)$items as $it) { $pid = isset($it['product_id']) ? (int)$it['product_id'] : 0; $vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0; $qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0; $var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array(); if ($pid && $qty > 0) { WC()->cart->add_to_cart($pid, $qty, $vid, $var); } } WC()->cart->calculate_totals(); } // Fix cart restoration logic add_action('wp_loaded', function() { if (!method_exists(WC()->session, 'get')) return; $token = WC()->session->get('pc_partial_token'); if ($token && WC()->cart->is_empty()) { $payload = get_transient(pc_transient_key($token)); if (!empty($payload['snapshot'])) { pc_restore_cart_from_items($payload['snapshot']); WC()->cart->calculate_totals(); } } }, 20); function pc_transient_key($token) { return 'pc_partial_payload_' . sanitize_key($token); } /* ------------------------------------------------- * AJAX: Local rehydrate when Woo cart is empty * ------------------------------------------------- */ add_action('wp_ajax_pc_rehydrate_cart', 'pc_rehydrate_cart'); add_action('wp_ajax_nopriv_pc_rehydrate_cart', 'pc_rehydrate_cart'); function pc_rehydrate_cart() { check_ajax_referer('woocommerce-cart', 'security'); $raw = isset($_POST['items']) ? wp_unslash($_POST['items']) : ''; $items = is_string($raw) ? json_decode($raw, true) : (array)$raw; if (!is_array($items)) { wp_send_json_error(array('message' => 'Invalid items.'), 400); } if (!WC()->cart) wc_load_cart(); if (!WC()->cart->is_empty()) { wp_send_json_success(array('message' => 'Cart not empty.')); } foreach ($items as $it) { $pid = isset($it['product_id']) ? (int)$it['product_id'] : 0; $vid = isset($it['variation_id']) ? (int)$it['variation_id'] : 0; $qty = isset($it['quantity']) ? wc_stock_amount($it['quantity']) : 0; $var = isset($it['variation']) && is_array($it['variation']) ? array_map('wc_clean', $it['variation']) : array(); if ($pid && $qty > 0) { WC()->cart->add_to_cart($pid, $qty, $vid, $var); } } WC()->cart->calculate_totals(); wp_send_json_success(array('rehydrated' => true)); } /* ------------------------------------------------- * AJAX: Update qty (per-row; no page reload) * ------------------------------------------------- */ add_action('wp_ajax_update_cart_item_qty', 'pc_update_cart_item_qty'); add_action('wp_ajax_nopriv_update_cart_item_qty', 'pc_update_cart_item_qty'); function pc_update_cart_item_qty() { check_ajax_referer('woocommerce-cart', 'security'); $key = isset($_POST['cart_item_key']) ? wc_clean(wp_unslash($_POST['cart_item_key'])) : ''; $qty = isset($_POST['qty']) ? wc_stock_amount($_POST['qty']) : null; if (!$key || $qty === null) { wp_send_json_error(array('message' => 'Missing params.'), 400); } if (!WC()->cart) wc_load_cart(); if ($qty <= 0) { $removed = WC()->cart->remove_cart_item($key); WC()->cart->calculate_totals(); wp_send_json_success(array('removed' => (bool)$removed)); } else { $set = WC()->cart->set_quantity($key, $qty, true); WC()->cart->calculate_totals(); $cart_item = WC()->cart->get_cart_item($key); if (!$cart_item) { wp_send_json_error(array('message' => 'Cart item not found after update.'), 404); } $_product = $cart_item['data']; $subtotal_html = apply_filters( 'woocommerce_cart_item_subtotal', WC()->cart->get_product_subtotal($_product, $cart_item['quantity']), $cart_item, $key ); // Use line_total + line_tax (after totals) for checkbox data-price $line_total_incl_tax = (float)($cart_item['line_total'] + $cart_item['line_tax']); wp_send_json_success(array( 'subtotal_html' => $subtotal_html, 'line_total_incl_tax' => $line_total_incl_tax, 'removed' => false, )); } } /* ------------------------------------------------- * AJAX: Remove selected * ------------------------------------------------- */ add_action('wp_ajax_remove_selected_cart_items', 'pc_remove_selected_cart_items'); add_action('wp_ajax_nopriv_remove_selected_cart_items', 'pc_remove_selected_cart_items'); function pc_remove_selected_cart_items() { check_ajax_referer('woocommerce-cart', 'security'); $keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array(); if (!WC()->cart) wc_load_cart(); foreach ($keys as $k) { $k = wc_clean(wp_unslash($k)); WC()->cart->remove_cart_item($k); } WC()->cart->calculate_totals(); wp_send_json_success(true); } /* ------------------------------------------------- * AJAX: Empty cart * ------------------------------------------------- */ add_action('wp_ajax_empty_cart', 'pc_empty_cart'); add_action('wp_ajax_nopriv_empty_cart', 'pc_empty_cart'); function pc_empty_cart() { check_ajax_referer('woocommerce-cart', 'security'); if (!WC()->cart) wc_load_cart(); WC()->cart->empty_cart(); wp_send_json_success(true); } /* ------------------------------------------------- * AJAX: Apply coupon * ------------------------------------------------- */ add_action('wp_ajax_apply_coupon', 'pc_apply_coupon'); add_action('wp_ajax_nopriv_apply_coupon', 'pc_apply_coupon'); function pc_apply_coupon() { check_ajax_referer('woocommerce-cart', 'security'); $code = isset($_POST['coupon_code']) ? wc_format_coupon_code(wp_unslash($_POST['coupon_code'])) : ''; if (!$code) { wp_send_json_error(array('message' => __('请输入优惠券代码', 'woocommerce')), 400); } if (!WC()->cart) wc_load_cart(); $applied = WC()->cart->apply_coupon($code); WC()->cart->calculate_totals(); if (is_wp_error($applied)) { wp_send_json_error(array('message' => $applied->get_error_message()), 400); } if (!$applied) { wp_send_json_error(array('message' => __('优惠券应用失败', 'woocommerce')), 400); } wp_send_json_success(true); } /* ------------------------------------------------- * AJAX: Start partial checkout to regular checkout page * ------------------------------------------------- */ add_action('wp_ajax_create_direct_order', 'pc_create_direct_order'); add_action('wp_ajax_nopriv_create_direct_order', 'pc_create_direct_order'); function pc_create_direct_order() { check_ajax_referer('woocommerce-cart', 'security'); $selected_keys = isset($_POST['selected_items']) ? (array) $_POST['selected_items'] : array(); if (empty($selected_keys)) { wp_send_json_error(array('message' => __('请选择要结算的商品', 'woocommerce')), 400); } if (!WC()->cart) wc_load_cart(); // Snapshot full cart $snapshot = pc_snapshot_current_cart(); // Build selected items from current cart based on cart_item_key list $selected = array(); foreach (WC()->cart->get_cart() as $ci_key => $ci) { if (!in_array($ci_key, $selected_keys, true)) { continue; } $pid = (int)$ci['product_id']; $vid = (int)$ci['variation_id']; $qty = wc_stock_amount($ci['quantity']); $var = isset($ci['variation']) && is_array($ci['variation']) ? array_map('wc_clean', $ci['variation']) : array(); if ($pid && $qty > 0) { $selected[] = array( 'product_id' => $pid, 'variation_id' => $vid, 'variation' => $var, 'quantity' => $qty, ); } } if (empty($selected)) { wp_send_json_error(array('message' => __('没有可结算的商品', 'woocommerce')), 400); } $token = wp_generate_uuid4(); $payload = array( 'uid' => pc_get_cart_uid(), 'snapshot' => $snapshot, 'selected' => $selected, 'created' => time(), ); set_transient(pc_transient_key($token), $payload, 2 * DAY_IN_SECONDS); // Put token in session (used across checkout AJAX calls) if (method_exists(WC()->session, 'set')) { WC()->session->set('pc_partial_token', $token); } $checkout_url = add_query_arg('pc_token', rawurlencode($token), wc_get_checkout_url()); wp_send_json_success(array('checkout_url' => $checkout_url)); } /* ------------------------------------------------- * Virtualize cart on checkout for token and rebuild after purchase * ------------------------------------------------- */ // Entering checkout with token: virtualize cart to selected items add_action('woocommerce_before_checkout_form', function() { if (!isset($_GET['pc_token'])) return; $token = sanitize_text_field(wp_unslash($_GET['pc_token'])); $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['selected'])) return; if (!WC()->cart) wc_load_cart(); // Virtualize to selected items only pc_restore_cart_from_items($payload['selected']); // Persist token in session for all subsequent checkout AJAX calls if (method_exists(WC()->session, 'set')) { WC()->session->set('pc_partial_token', $token); } }, 1); // Safety: just-in-time re-virtualization before order processing add_action('woocommerce_before_checkout_process', function() { if (!method_exists(WC()->session, 'get')) return; $token = WC()->session->get('pc_partial_token'); if (!$token) return; $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['selected'])) return; // Ensure cart still equals selected set pc_restore_cart_from_items($payload['selected']); }, 1); // Tag order with token add_action('woocommerce_checkout_create_order', function($order) { $token = null; if (isset($_GET['pc_token'])) { $token = sanitize_text_field(wp_unslash($_GET['pc_token'])); } elseif (method_exists(WC()->session, 'get')) { $token = WC()->session->get('pc_partial_token'); } if ($token) { $order->update_meta_data('_pc_partial_token', $token); $order->update_meta_data('_pc_cart_snapshot', $token); } }, 10, 1); // Fixed: Only remove purchased items from cart add_action('woocommerce_thankyou', function($order_id) { $order = wc_get_order($order_id); if (!$order) return; $token = $order->get_meta('_pc_partial_token'); if (!$token) return; $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['snapshot'])) { if (method_exists(WC()->session, 'set')) { WC()->session->set('pc_partial_token', null); } delete_transient(pc_transient_key($token)); return; } // 1. Restore FULL snapshot (all items) pc_restore_cart_from_items($payload['snapshot']); WC()->cart->calculate_totals(); // 2. Remove only the purchased (selected) items foreach ($payload['selected'] as $selected_item) { $cart_item_key = pc_find_cart_item($selected_item['product_id'], $selected_item['variation_id']); if ($cart_item_key) { WC()->cart->remove_cart_item($cart_item_key); } } // 3. Update cart totals WC()->cart->calculate_totals(); // Cleanup token if (method_exists(WC()->session, 'set')) { WC()->session->set('pc_partial_token', null); } delete_transient(pc_transient_key($token)); }, 20); // Helper: Find cart item by product and variation ID function pc_find_cart_item($product_id, $variation_id = 0) { if (!WC()->cart) wc_load_cart(); foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) { $cart_pid = isset($cart_item['product_id']) ? (int)$cart_item['product_id'] : 0; $cart_vid = isset($cart_item['variation_id']) ? (int)$cart_item['variation_id'] : 0; if ($cart_pid == $product_id && $cart_vid == $variation_id) { return $cart_item_key; } } return false; } // Visiting cart with active token: restore full snapshot (cancel/back) add_action('woocommerce_before_cart', function() { if (!method_exists(WC()->session, 'get')) return; $token = WC()->session->get('pc_partial_token'); if (!$token) return; $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['snapshot'])) return; // Restore full snapshot so cart page always shows everything pc_restore_cart_from_items($payload['snapshot']); }, 1); /* ------------------------------------------------- * Keep cart count accurate during checkout process * ------------------------------------------------- */ add_filter('woocommerce_cart_contents_count', function($count) { // Check if partial token exists if (!method_exists(WC()->session, 'get')) return $count; $token = WC()->session->get('pc_partial_token'); if ($token) { $payload = get_transient(pc_transient_key($token)); // Always show full cart count even during checkout if (!empty($payload['snapshot'])) { $snapshot_count = 0; foreach ($payload['snapshot'] as $item) { $snapshot_count += (int)$item['quantity']; } return $snapshot_count; } } return $count; }); // Ensure cart item totals are accurate in header add_action('woocommerce_before_cart', function() { pc_maintain_cart_consistency(); }); add_action('woocommerce_before_checkout_form', function() { pc_maintain_cart_consistency(); }); function pc_maintain_cart_consistency() { if (!method_exists(WC()->session, 'get')) return; $token = WC()->session->get('pc_partial_token'); if (!$token) return; $payload = get_transient(pc_transient_key($token)); if (empty($payload) || empty($payload['snapshot'])) return; // Immediately restore full cart for consistent UI pc_restore_cart_from_items($payload['snapshot']); WC()->cart->calculate_totals(); }
09-06
private void performTraversals() { 3295 mLastPerformTraversalsSkipDrawReason = null; 3296 3297 // cache mView since it is used so much below... 3298 final View host = mView; 3299 if (DBG) { 3300 System.out.println("======================================"); 3301 System.out.println("performTraversals"); 3302 host.debug(); 3303 } 3304 3305 if (host == null || !mAdded) { 3306 mLastPerformTraversalsSkipDrawReason = host == null ? "no_host" : "not_added"; 3307 return; 3308 } 3309 3310 if (mNumPausedForSync > 0) { 3311 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 3312 Trace.instant(Trace.TRACE_TAG_VIEW, 3313 TextUtils.formatSimple("performTraversals#mNumPausedForSync=%d", 3314 mNumPausedForSync)); 3315 } 3316 3317 Log.d(mTag, "Skipping traversal due to sync " + mNumPausedForSync); 3318 mLastPerformTraversalsSkipDrawReason = "paused_for_sync"; 3319 return; 3320 } 3321 3322 // #ifdef OPLUS_FEATURE_VIEW_DEBUG 3323 // Bard.Zhang@Android.UIFramework 2023-01-28 Add for : view debug 3324 mViewRootImplExt.markOnPerformTraversalsStart(host, mFirst); 3325 // #endif /* OPLUS_FEATURE_VIEW_DEBUG */ 3326 3327 mIsInTraversal = true; 3328 mWillDrawSoon = true; 3329 boolean cancelDraw = false; 3330 String cancelReason = null; 3331 boolean isSyncRequest = false; 3332 3333 boolean windowSizeMayChange = false; 3334 WindowManager.LayoutParams lp = mWindowAttributes; 3335 3336 int desiredWindowWidth; 3337 int desiredWindowHeight; 3338 3339 final int viewVisibility = getHostVisibility(); 3340 final boolean viewVisibilityChanged = !mFirst 3341 && (mViewVisibility != viewVisibility || mNewSurfaceNeeded 3342 // Also check for possible double visibility update, which will make current 3343 // viewVisibility value equal to mViewVisibility and we may miss it. 3344 || mAppVisibilityChanged); 3345 mAppVisibilityChanged = false; 3346 final boolean viewUserVisibilityChanged = !mFirst && 3347 ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE)); 3348 final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp); 3349 3350 WindowManager.LayoutParams params = null; 3351 CompatibilityInfo compatibilityInfo = 3352 mDisplay.getDisplayAdjustments().getCompatibilityInfo(); 3353 if (compatibilityInfo.supportsScreen() == mLastInCompatMode) { 3354 params = lp; 3355 mFullRedrawNeeded = true; 3356 mLayoutRequested = true; 3357 if (mLastInCompatMode) { 3358 params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; 3359 mLastInCompatMode = false; 3360 } else { 3361 params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; 3362 mLastInCompatMode = true; 3363 } 3364 } 3365 3366 Rect frame = mWinFrame; 3367 if (mFirst) { 3368 mFullRedrawNeeded = true; 3369 mLayoutRequested = true; 3370 3371 final Configuration config = getConfiguration(); 3372 if (shouldUseDisplaySize(lp)) { 3373 // NOTE -- system code, won't try to do compat mode. 3374 Point size = new Point(); 3375 mDisplay.getRealSize(size); 3376 desiredWindowWidth = size.x; 3377 desiredWindowHeight = size.y; 3378 } else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT 3379 || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { 3380 // For wrap content, we have to remeasure later on anyways. Use size consistent with 3381 // below so we get best use of the measure cache. 3382 final Rect bounds = getWindowBoundsInsetSystemBars(); 3383 desiredWindowWidth = bounds.width(); 3384 desiredWindowHeight = bounds.height(); 3385 } else { 3386 // After addToDisplay, the frame contains the frameHint from window manager, which 3387 // for most windows is going to be the same size as the result of relayoutWindow. 3388 // Using this here allows us to avoid remeasuring after relayoutWindow 3389 desiredWindowWidth = frame.width(); 3390 desiredWindowHeight = frame.height(); 3391 } 3392 3393 // We used to use the following condition to choose 32 bits drawing caches: 3394 // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888 3395 // However, windows are now always 32 bits by default, so choose 32 bits 3396 mAttachInfo.mUse32BitDrawingCache = true; 3397 mAttachInfo.mWindowVisibility = viewVisibility; 3398 mAttachInfo.mRecomputeGlobalAttributes = false; 3399 mLastConfigurationFromResources.setTo(config); 3400 mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; 3401 // Set the layout direction if it has not been set before (inherit is the default) 3402 if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) { 3403 host.setLayoutDirection(config.getLayoutDirection()); 3404 } 3405 //#ifdef OPLUS_FEATURE_BRACKETMODE_2_0 3406 //wenguangyu@ANDROID.WMS, 2022/10/02, add for bracket mode 3407 mViewRootImplExt.attachToWindow(); 3408 //#endif /*OPLUS_FEATURE_BRACKETMODE_2_0*/ 3409 host.dispatchAttachedToWindow(mAttachInfo, 0); 3410 mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); 3411 dispatchApplyInsets(host); 3412 if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) { 3413 // For apps requesting legacy back behavior, we add a compat callback that 3414 // dispatches {@link KeyEvent#KEYCODE_BACK} to their root views. 3415 // This way from system point of view, these apps are providing custom 3416 // {@link OnBackInvokedCallback}s, and will not play system back animations 3417 // for them. 3418 registerCompatOnBackInvokedCallback(); 3419 } 3420 } else { 3421 desiredWindowWidth = frame.width(); 3422 desiredWindowHeight = frame.height(); 3423 if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { 3424 if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame); 3425 mFullRedrawNeeded = true; 3426 mLayoutRequested = true; 3427 windowSizeMayChange = true; 3428 } 3429 } 3430 3431 if (viewVisibilityChanged) { 3432 mAttachInfo.mWindowVisibility = viewVisibility; 3433 host.dispatchWindowVisibilityChanged(viewVisibility); 3434 mAttachInfo.mTreeObserver.dispatchOnWindowVisibilityChange(viewVisibility); 3435 if (viewUserVisibilityChanged) { 3436 host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE); 3437 } 3438 if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) { 3439 endDragResizing(); 3440 destroyHardwareResources(); 3441 } 3442 } 3443 3444 // Non-visible windows can't hold accessibility focus. 3445 if (mAttachInfo.mWindowVisibility != View.VISIBLE) { 3446 host.clearAccessibilityFocus(); 3447 } 3448 3449 // Execute enqueued actions on every traversal in case a detached view enqueued an action 3450 getRunQueue().executeActions(mAttachInfo.mHandler); 3451 3452 if (mFirst) { 3453 // make sure touch mode code executes by setting cached value 3454 // to opposite of the added touch mode. 3455 mAttachInfo.mInTouchMode = !mAddedTouchMode; 3456 ensureTouchModeLocally(mAddedTouchMode); 3457 } 3458 3459 boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); 3460 if (layoutRequested) { 3461 if (!mFirst) { 3462 if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT 3463 || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { 3464 windowSizeMayChange = true; 3465 3466 if (shouldUseDisplaySize(lp)) { 3467 // NOTE -- system code, won't try to do compat mode. 3468 Point size = new Point(); 3469 mDisplay.getRealSize(size); 3470 desiredWindowWidth = size.x; 3471 desiredWindowHeight = size.y; 3472 } else { 3473 final Rect bounds = getWindowBoundsInsetSystemBars(); 3474 desiredWindowWidth = bounds.width(); 3475 desiredWindowHeight = bounds.height(); 3476 } 3477 } 3478 } 3479 3480 // Ask host how big it wants to be 3481 windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(), 3482 desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure); 3483 } 3484 3485 if (collectViewAttributes()) { 3486 params = lp; 3487 } 3488 if (mAttachInfo.mForceReportNewAttributes) { 3489 mAttachInfo.mForceReportNewAttributes = false; 3490 params = lp; 3491 } 3492 3493 if (mFirst || mAttachInfo.mViewVisibilityChanged) { 3494 mAttachInfo.mViewVisibilityChanged = false; 3495 int resizeMode = mSoftInputMode & SOFT_INPUT_MASK_ADJUST; 3496 // If we are in auto resize mode, then we need to determine 3497 // what mode to use now. 3498 if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) { 3499 final int N = mAttachInfo.mScrollContainers.size(); 3500 for (int i=0; i<N; i++) { 3501 if (mAttachInfo.mScrollContainers.get(i).isShown()) { 3502 resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 3503 } 3504 } 3505 if (resizeMode == 0) { 3506 resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; 3507 } 3508 if ((lp.softInputMode & SOFT_INPUT_MASK_ADJUST) != resizeMode) { 3509 lp.softInputMode = (lp.softInputMode & ~SOFT_INPUT_MASK_ADJUST) | resizeMode; 3510 params = lp; 3511 } 3512 } 3513 } 3514 3515 if (mApplyInsetsRequested) { 3516 dispatchApplyInsets(host); 3517 if (mLayoutRequested) { 3518 // Short-circuit catching a new layout request here, so 3519 // we don't need to go through two layout passes when things 3520 // change due to fitting system windows, which can happen a lot. 3521 windowSizeMayChange |= measureHierarchy(host, lp, 3522 mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight, 3523 shouldOptimizeMeasure); 3524 } 3525 } 3526 3527 if (layoutRequested) { 3528 // Clear this now, so that if anything requests a layout in the 3529 // rest of this function we will catch it and re-run a full 3530 // layout pass. 3531 mLayoutRequested = false; 3532 } 3533 3534 boolean windowShouldResize = layoutRequested && windowSizeMayChange 3535 && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) 3536 || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT && 3537 frame.width() < desiredWindowWidth && frame.width() != mWidth) 3538 || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT && 3539 frame.height() < desiredWindowHeight && frame.height() != mHeight)); 3540 windowShouldResize |= mDragResizing && mPendingDragResizing; 3541 3542 // Determine whether to compute insets. 3543 // If there are no inset listeners remaining then we may still need to compute 3544 // insets in case the old insets were non-empty and must be reset. 3545 final boolean computesInternalInsets = 3546 mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners() 3547 || mAttachInfo.mHasNonEmptyGivenInternalInsets; 3548 3549 boolean insetsPending = false; 3550 int relayoutResult = 0; 3551 boolean updatedConfiguration = false; 3552 3553 final int surfaceGenerationId = mSurface.getGenerationId(); 3554 3555 final boolean isViewVisible = viewVisibility == View.VISIBLE; 3556 boolean surfaceSizeChanged = false; 3557 boolean surfaceCreated = false; 3558 boolean surfaceDestroyed = false; 3559 // True if surface generation id changes or relayout result is RELAYOUT_RES_SURFACE_CHANGED. 3560 boolean surfaceReplaced = false; 3561 3562 final boolean windowAttributesChanged = mWindowAttributesChanged; 3563 if (windowAttributesChanged) { 3564 mWindowAttributesChanged = false; 3565 params = lp; 3566 } 3567 3568 if (params != null) { 3569 if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0 3570 && !PixelFormat.formatHasAlpha(params.format)) { 3571 params.format = PixelFormat.TRANSLUCENT; 3572 } 3573 adjustLayoutParamsForCompatibility(params); 3574 controlInsetsForCompatibility(params); 3575 if (mDispatchedSystemBarAppearance != params.insetsFlags.appearance) { 3576 mDispatchedSystemBarAppearance = params.insetsFlags.appearance; 3577 mView.onSystemBarAppearanceChanged(mDispatchedSystemBarAppearance); 3578 } 3579 } 3580 3581 if (mFirst || windowShouldResize || viewVisibilityChanged || params != null 3582 || mForceNextWindowRelayout) { 3583 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 3584 Trace.traceBegin(Trace.TRACE_TAG_VIEW, 3585 TextUtils.formatSimple("relayoutWindow#" 3586 + "first=%b/resize=%b/vis=%b/params=%b/force=%b", 3587 mFirst, windowShouldResize, viewVisibilityChanged, params != null, 3588 mForceNextWindowRelayout)); 3589 } 3590 3591 mForceNextWindowRelayout = false; 3592 3593 // If this window is giving internal insets to the window manager, then we want to first 3594 // make the provided insets unchanged during layout. This avoids it briefly causing 3595 // other windows to resize/move based on the raw frame of the window, waiting until we 3596 // can finish laying out this window and get back to the window manager with the 3597 // ultimately computed insets. 3598 insetsPending = computesInternalInsets; 3599 3600 if (mSurfaceHolder != null) { 3601 mSurfaceHolder.mSurfaceLock.lock(); 3602 mDrawingAllowed = true; 3603 } 3604 3605 boolean hwInitialized = false; 3606 boolean dispatchApplyInsets = false; 3607 boolean hadSurface = mSurface.isValid(); 3608 3609 try { 3610 if (DEBUG_LAYOUT) { 3611 Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" + 3612 host.getMeasuredHeight() + ", params=" + params); 3613 } 3614 3615 if (mFirst || viewVisibilityChanged) { 3616 mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED; 3617 } 3618 //#ifdef OPLUS_FEATURE_JANK_TRACKER 3619 //wangwei11@oppo.com, 2021/11/28, Add for janktracker 3620 mChoreographer.mChoreographerExt.markRelayout(); 3621 //#endif /*OPLUS_FEATURE_JANK_TRACKER*/ 3622 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); 3623 cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW) 3624 == RELAYOUT_RES_CANCEL_AND_REDRAW; 3625 cancelReason = "relayout"; 3626 final boolean dragResizing = mPendingDragResizing; 3627 if (mSyncSeqId > mLastSyncSeqId) { 3628 mLastSyncSeqId = mSyncSeqId; 3629 if (DEBUG_BLAST) { 3630 Log.d(mTag, "Relayout called with blastSync"); 3631 } 3632 reportNextDraw("relayout"); 3633 mSyncBuffer = true; 3634 isSyncRequest = true; 3635 if (!cancelDraw) { 3636 mDrewOnceForSync = false; 3637 } 3638 } 3639 3640 final boolean surfaceControlChanged = 3641 (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED) 3642 == RELAYOUT_RES_SURFACE_CHANGED; 3643 3644 if (mSurfaceControl.isValid()) { 3645 updateOpacity(mWindowAttributes, dragResizing, 3646 surfaceControlChanged /*forceUpdate */); 3647 // No need to updateDisplayDecoration if it's a new SurfaceControl and 3648 // mDisplayDecorationCached is false, since that's the default for a new 3649 // SurfaceControl. 3650 if (surfaceControlChanged && mDisplayDecorationCached) { 3651 updateDisplayDecoration(); 3652 } 3653 if (surfaceControlChanged 3654 && mWindowAttributes.type 3655 == WindowManager.LayoutParams.TYPE_STATUS_BAR) { 3656 mTransaction.setDefaultFrameRateCompatibility(mSurfaceControl, 3657 Surface.FRAME_RATE_COMPATIBILITY_NO_VOTE).apply(); 3658 } 3659 } 3660 3661 if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString() 3662 + " surface=" + mSurface); 3663 3664 // If the pending {@link MergedConfiguration} handed back from 3665 // {@link #relayoutWindow} does not match the one last reported, 3666 // WindowManagerService has reported back a frame from a configuration not yet 3667 // handled by the client. In this case, we need to accept the configuration so we 3668 // do not lay out and draw with the wrong configuration. 3669 if (mRelayoutRequested 3670 && !mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) { 3671 if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: " 3672 + mPendingMergedConfiguration.getMergedConfiguration()); 3673 performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration), 3674 !mFirst, INVALID_DISPLAY /* same display */); 3675 updatedConfiguration = true; 3676 } 3677 final boolean updateSurfaceNeeded = mUpdateSurfaceNeeded; 3678 mUpdateSurfaceNeeded = false; 3679 3680 surfaceSizeChanged = false; 3681 if (!mLastSurfaceSize.equals(mSurfaceSize)) { 3682 surfaceSizeChanged = true; 3683 mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y); 3684 } 3685 final boolean alwaysConsumeSystemBarsChanged = 3686 mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars; 3687 updateColorModeIfNeeded(lp.getColorMode()); 3688 surfaceCreated = !hadSurface && mSurface.isValid(); 3689 surfaceDestroyed = hadSurface && !mSurface.isValid(); 3690 3691 // When using Blast, the surface generation id may not change when there's a new 3692 // SurfaceControl. In that case, we also check relayout flag 3693 // RELAYOUT_RES_SURFACE_CHANGED since it should indicate that WMS created a new 3694 // SurfaceControl. 3695 surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId() 3696 || surfaceControlChanged) && mSurface.isValid(); 3697 if (surfaceReplaced) { 3698 mSurfaceSequenceId++; 3699 } 3700 if (alwaysConsumeSystemBarsChanged) { 3701 mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars; 3702 dispatchApplyInsets = true; 3703 } 3704 if (updateCaptionInsets()) { 3705 dispatchApplyInsets = true; 3706 } 3707 if (dispatchApplyInsets || mLastSystemUiVisibility != 3708 mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested) { 3709 mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; 3710 dispatchApplyInsets(host); 3711 // We applied insets so force contentInsetsChanged to ensure the 3712 // hierarchy is measured below. 3713 dispatchApplyInsets = true; 3714 } 3715 3716 if (surfaceCreated) { 3717 // If we are creating a new surface, then we need to 3718 // completely redraw it. 3719 mFullRedrawNeeded = true; 3720 mPreviousTransparentRegion.setEmpty(); 3721 3722 // Only initialize up-front if transparent regions are not 3723 // requested, otherwise defer to see if the entire window 3724 // will be transparent 3725 if (mAttachInfo.mThreadedRenderer != null) { 3726 try { 3727 hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface); 3728 // #ifdef OPLUS_EXTENSION_HOOK 3729 // yanliang@Android.Performance 2023-02-13 Add for Optimize sliding effect 3730 mViewRootImplExt.setPendingBufferCountSetting(true); 3731 // #endif /*OPLUS_EXTENSION_HOOK*/ 3732 if (hwInitialized && (host.mPrivateFlags 3733 //#ifndef OPLUS_BUG_STABILITY 3734 //Weitao.Chen@ANDROID.STABILITY.2742412, 2020/01/02, Add for null pointer exp 3735 //& View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) { 3736 //#else 3737 & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0 && mAttachInfo.mThreadedRenderer != null) { 3738 //#endif /*OPLUS_BUG_STABILITY*/ 3739 // Don't pre-allocate if transparent regions 3740 // are requested as they may not be needed 3741 mAttachInfo.mThreadedRenderer.allocateBuffers(); 3742 } 3743 } catch (OutOfResourcesException e) { 3744 handleOutOfResourcesException(e); 3745 mLastPerformTraversalsSkipDrawReason = "oom_initialize_renderer"; 3746 return; 3747 } 3748 } 3749 } else if (surfaceDestroyed) { 3750 // If the surface has been removed, then reset the scroll 3751 // positions. 3752 if (mLastScrolledFocus != null) { 3753 mLastScrolledFocus.clear(); 3754 } 3755 mScrollY = mCurScrollY = 0; 3756 if (mView instanceof RootViewSurfaceTaker) { 3757 ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY); 3758 } 3759 if (mScroller != null) { 3760 mScroller.abortAnimation(); 3761 } 3762 // Our surface is gone 3763 if (isHardwareEnabled()) { 3764 mAttachInfo.mThreadedRenderer.destroy(); 3765 } 3766 } else if ((surfaceReplaced || surfaceSizeChanged || updateSurfaceNeeded) 3767 && mSurfaceHolder == null 3768 && mAttachInfo.mThreadedRenderer != null 3769 && mSurface.isValid()) { 3770 mFullRedrawNeeded = true; 3771 try { 3772 // Need to do updateSurface (which leads to CanvasContext::setSurface and 3773 // re-create the EGLSurface) if either the Surface changed (as indicated by 3774 // generation id), or WindowManager changed the surface size. The latter is 3775 // because on some chips, changing the consumer side's BufferQueue size may 3776 // not take effect immediately unless we create a new EGLSurface. 3777 // Note that frame size change doesn't always imply surface size change (eg. 3778 // drag resizing uses fullscreen surface), need to check surfaceSizeChanged 3779 // flag from WindowManager. 3780 mAttachInfo.mThreadedRenderer.updateSurface(mSurface); 3781 } catch (OutOfResourcesException e) { 3782 handleOutOfResourcesException(e); 3783 mLastPerformTraversalsSkipDrawReason = "oom_update_surface"; 3784 return; 3785 } 3786 } 3787 3788 if (mDragResizing != dragResizing) { 3789 if (dragResizing) { 3790 final boolean backdropSizeMatchesFrame = 3791 mWinFrame.width() == mPendingBackDropFrame.width() 3792 && mWinFrame.height() == mPendingBackDropFrame.height(); 3793 // TODO: Need cutout? 3794 startDragResizing(mPendingBackDropFrame, !backdropSizeMatchesFrame, 3795 mAttachInfo.mContentInsets, mAttachInfo.mStableInsets); 3796 } else { 3797 // We shouldn't come here, but if we come we should end the resize. 3798 endDragResizing(); 3799 } 3800 } 3801 if (!mUseMTRenderer) { 3802 if (dragResizing) { 3803 mCanvasOffsetX = mWinFrame.left; 3804 mCanvasOffsetY = mWinFrame.top; 3805 } else { 3806 mCanvasOffsetX = mCanvasOffsetY = 0; 3807 } 3808 } 3809 } catch (RemoteException e) { 3810 } finally { 3811 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { 3812 Trace.traceEnd(Trace.TRACE_TAG_VIEW); 3813 } 3814 } 3815 3816 if (DEBUG_ORIENTATION) Log.v( 3817 TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface); 3818 3819 mAttachInfo.mWindowLeft = frame.left; 3820 mAttachInfo.mWindowTop = frame.top; 3821 3822 // !!FIXME!! This next section handles the case where we did not get the 3823 // window size we asked for. We should avoid this by getting a maximum size from 3824 // the window session beforehand. 3825 if (mWidth != frame.width() || mHeight != frame.height()) { 3826 mWidth = frame.width(); 3827 mHeight = frame.height(); 3828 } 3829 3830 if (mSurfaceHolder != null) { 3831 // The app owns the surface; tell it about what is going on. 3832 if (mSurface.isValid()) { 3833 // XXX .copyFrom() doesn't work! 3834 //mSurfaceHolder.mSurface.copyFrom(mSurface); 3835 mSurfaceHolder.mSurface = mSurface; 3836 } 3837 mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight); 3838 mSurfaceHolder.mSurfaceLock.unlock(); 3839 if (surfaceCreated) { 3840 mSurfaceHolder.ungetCallbacks(); 3841 3842 mIsCreating = true; 3843 SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks(); 3844 if (callbacks != null) { 3845 for (SurfaceHolder.Callback c : callbacks) { 3846 c.surfaceCreated(mSurfaceHolder); 3847 } 3848 } 3849 } 3850 3851 if ((surfaceCreated || surfaceReplaced || surfaceSizeChanged 3852 || windowAttributesChanged) && mSurface.isValid()) { 3853 SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks(); 3854 if (callbacks != null) { 3855 for (SurfaceHolder.Callback c : callbacks) { 3856 c.surfaceChanged(mSurfaceHolder, lp.format, 3857 mWidth, mHeight); 3858 } 3859 } 3860 mIsCreating = false; 3861 } 3862 3863 if (surfaceDestroyed) { 3864 notifyHolderSurfaceDestroyed(); 3865 mSurfaceHolder.mSurfaceLock.lock(); 3866 try { 3867 mSurfaceHolder.mSurface = new Surface(); 3868 } finally { 3869 mSurfaceHolder.mSurfaceLock.unlock(); 3870 } 3871 } 3872 } 3873 3874 final ThreadedRenderer threadedRenderer = mAttachInfo.mThreadedRenderer; 3875 if (threadedRenderer != null && threadedRenderer.isEnabled()) { 3876 if (hwInitialized 3877 || mWidth != threadedRenderer.getWidth() 3878 || mHeight != threadedRenderer.getHeight() 3879 || mNeedsRendererSetup) { 3880 threadedRenderer.setup(mWidth, mHeight, mAttachInfo, 3881 mWindowAttributes.surfaceInsets); 3882 mNeedsRendererSetup = false; 3883 } 3884 } 3885 3886 // TODO: In the CL "ViewRootImpl: Fix issue with early draw report in 3887 // seamless rotation". We moved processing of RELAYOUT_RES_BLAST_SYNC 3888 // earlier in the function, potentially triggering a call to 3889 // reportNextDraw(). That same CL changed this and the next reference 3890 // to wasReportNextDraw, such that this logic would remain undisturbed 3891 // (it continues to operate as if the code was never moved). This was 3892 // done to achieve a more hermetic fix for S, but it's entirely 3893 // possible that checking the most recent value is actually more 3894 // correct here. 3895 if (!mStopped || mReportNextDraw) { 3896 if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() 3897 || dispatchApplyInsets || updatedConfiguration) { 3898 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width, 3899 lp.privateFlags); 3900 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height, 3901 lp.privateFlags); 3902 3903 if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth=" 3904 + mWidth + " measuredWidth=" + host.getMeasuredWidth() 3905 + " mHeight=" + mHeight 3906 + " measuredHeight=" + host.getMeasuredHeight() 3907 + " dispatchApplyInsets=" + dispatchApplyInsets); 3908 3909 // #ifdef OPLUS_FEATURE_VIEW_DEBUG 3910 // Bard.Zhang@Android.UIFramework 2023-01-19 Add for : view debug 3911 String measureReason = "Measure reason mFirst " + mFirst 3912 + " windowShouldResize " + windowShouldResize 3913 + " viewVisibilityChanged " + viewVisibilityChanged 3914 + " params != null " + (params != null) 3915 + " mForceNextWindowRelayout " + mForceNextWindowRelayout 3916 + " mStopped " + mStopped + " mReportNextDraw " + mReportNextDraw 3917 + " mWidth " + mWidth + " host.getMeasuredWidth " + host.getMeasuredWidth() 3918 + " mHeight " + mHeight + " host.getMeasuredHeight " + host.getMeasuredHeight() 3919 + " dispatchApplyInsets " + dispatchApplyInsets 3920 + " updateConfiguration " + updatedConfiguration; 3921 mViewRootImplExt.markPerformMeasureReason(measureReason); 3922 // #endif /* OPLUS_FEATURE_VIEW_DEBUG */ 3923 // #ifdef OPLUS_BUG_COMPATIBILITY 3924 //Qian.Jiang@Android.Wms, 2023/05/19: fix bug-5573936 3925 int heightMesure = MeasureSpec.getSize(childHeightMeasureSpec); 3926 int widthMesure = MeasureSpec.getSize(childWidthMeasureSpec); 3927 if (PANIC_DEBUG && (heightMesure > LIMIT || widthMesure > LIMIT)) { 3928 Log.d(mTag, "performMeasure heightMesure = " + heightMesure + ",widthMesure = " + widthMesure); 3929 } 3930 //#endif /* OPLUS_BUG_COMPATIBILITY */ 3931 // Ask host how big it wants to be 3932 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 3933 3934 // Implementation of weights from WindowManager.LayoutParams 3935 // We just grow the dimensions as needed and re-measure if 3936 // needs be 3937 int width = host.getMeasuredWidth(); 3938 int height = host.getMeasuredHeight(); 3939 boolean measureAgain = false; 3940 3941 if (lp.horizontalWeight > 0.0f) { 3942 width += (int) ((mWidth - width) * lp.horizontalWeight); 3943 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, 3944 MeasureSpec.EXACTLY); 3945 measureAgain = true; 3946 } 3947 if (lp.verticalWeight > 0.0f) { 3948 height += (int) ((mHeight - height) * lp.verticalWeight); 3949 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, 3950 MeasureSpec.EXACTLY); 3951 measureAgain = true; 3952 } 3953 3954 if (measureAgain) { 3955 if (DEBUG_LAYOUT) Log.v(mTag, 3956 "And hey let's measure once more: width=" + width 3957 + " height=" + height); 3958 3959 // #ifdef OPLUS_FEATURE_VIEW_DEBUG 3960 // Bard.Zhang@Android.UIFramework 2023-01-19 Add for : view debug 3961 mMeasureReason = "3"; 3962 String measureAgainReason = "Measure again lp.horizontalWeight " + lp.horizontalWeight 3963 + " lp.verticalWeight " + lp.verticalWeight; 3964 mViewRootImplExt.markPerformMeasureReason(measureAgainReason); 3965 // #endif /* OPLUS_FEATURE_VIEW_DEBUG */ 3966 3967 // #ifdef OPLUS_BUG_COMPATIBILITY 3968 //Qian.Jiang@Android.Wms, 2023/05/19: fix bug-5573936 3969 int heightAgain = MeasureSpec.getSize(childHeightMeasureSpec); 3970 int widthAgain = MeasureSpec.getSize(childWidthMeasureSpec); 3971 if (PANIC_DEBUG && (heightAgain > LIMIT || widthAgain > LIMIT)) { 3972 Log.d(mTag, "measureAgain performMeasure heightAgain = " + heightAgain + ",widthAgain = " + widthAgain); 3973 } 3974 //#endif /* OPLUS_BUG_COMPATIBILITY */ 3975 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 3976 } 3977 3978 layoutRequested = true; 3979 } 3980 } 3981 } else { 3982 // Not the first pass and no window/insets/visibility change but the window 3983 // may have moved and we need check that and if so to update the left and right 3984 // in the attach info. We translate only the window frame since on window move 3985 // the window manager tells us only for the new frame but the insets are the 3986 // same and we do not want to translate them more than once. 3987 maybeHandleWindowMove(frame); 3988 } 3989 3990 if (mViewMeasureDeferred) { 3991 // It's time to measure the views since we are going to layout them. 3992 performMeasure( 3993 MeasureSpec.makeMeasureSpec(frame.width(), MeasureSpec.EXACTLY), 3994 MeasureSpec.makeMeasureSpec(frame.height(), MeasureSpec.EXACTLY)); 3995 } 3996 3997 if (!mRelayoutRequested && mCheckIfCanDraw) { 3998 // We had a sync previously, but we didn't call IWindowSession#relayout in this 3999 // traversal. So we don't know if the sync is complete that we can continue to draw. 4000 // Here invokes cancelDraw to obtain the information. 4001 try { 4002 cancelDraw = mWindowSession.cancelDraw(mWindow); 4003 cancelReason = "wm_sync"; 4004 Log.d(mTag, "cancelDraw returned " + cancelDraw); 4005 } catch (RemoteException e) { 4006 } 4007 } 4008 4009 if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || 4010 windowAttributesChanged || mChildBoundingInsetsChanged) { 4011 // If the surface has been replaced, there's a chance the bounds layer is not parented 4012 // to the new layer. When updating bounds layer, also reparent to the main VRI 4013 // SurfaceControl to ensure it's correctly placed in the hierarchy. 4014 // 4015 // This needs to be done on the client side since WMS won't reparent the children to the 4016 // new surface if it thinks the app is closing. WMS gets the signal that the app is 4017 // stopping, but on the client side it doesn't get stopped since it's restarted quick 4018 // enough. WMS doesn't want to keep around old children since they will leak when the 4019 // client creates new children. 4020 prepareSurfaces(); 4021 mChildBoundingInsetsChanged = false; 4022 } 4023 4024 final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); 4025 boolean triggerGlobalLayoutListener = didLayout 4026 || mAttachInfo.mRecomputeGlobalAttributes; 4027 if (didLayout) { 4028 //#ifdef OPLUS_EXTENSION_HOOK 4029 //Jie.Zhang@ANDROID.PERFORMANCE, 2021/12/14, Add for quality information update 4030 final long startTime = SystemClock.uptimeMillis(); 4031 performLayout(lp, mWidth, mHeight); 4032 ExtLoader.type(IOplusJankMonitorExt.class).create().setLaunchStageTime(ActivityThread.currentProcessName(), "performLayout", startTime); 4033 //#endif /* OPLUS_EXTENSION_HOOK */ 4034 // #ifdef OPLUS_EXTENSION_HOOK 4035 // Guofu.Yang@ANDROID.WMS, 2018/02/23, [OSP-1634] add for Screen mode. 4036 mViewRootImplExt.setRefreshRateIfNeed((mView instanceof ViewGroup), mContext, 4037 mView, mWindow); 4038 // #endif /* OPLUS_EXTENSION_HOOK */ 4039 4040 // By this point all views have been sized and positioned 4041 // We can compute the transparent area 4042 4043 if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) { 4044 // start out transparent 4045 // TODO: AVOID THAT CALL BY CACHING THE RESULT? 4046 host.getLocationInWindow(mTmpLocation); 4047 mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1], 4048 mTmpLocation[0] + host.mRight - host.mLeft, 4049 mTmpLocation[1] + host.mBottom - host.mTop); 4050 4051 host.gatherTransparentRegion(mTransparentRegion); 4052 if (mTranslator != null) { 4053 mTranslator.translateRegionInWindowToScreen(mTransparentRegion); 4054 } 4055 4056 if (!mTransparentRegion.equals(mPreviousTransparentRegion)) { 4057 mPreviousTransparentRegion.set(mTransparentRegion); 4058 mFullRedrawNeeded = true; 4059 // TODO: Ideally we would do this in prepareSurfaces, 4060 // but prepareSurfaces is currently working under 4061 // the assumption that we paused the render thread 4062 // via the WM relayout code path. We probably eventually 4063 // want to synchronize transparent region hint changes 4064 // with draws. 4065 SurfaceControl sc = getSurfaceControl(); 4066 if (sc.isValid()) { 4067 mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply(); 4068 } 4069 } 4070 } 4071 4072 if (DBG) { 4073 System.out.println("======================================"); 4074 System.out.println("performTraversals -- after setFrame"); 4075 host.debug(); 4076 } 4077 } 4078 4079 boolean didUseTransaction = false; 4080 // These callbacks will trigger SurfaceView SurfaceHolder.Callbacks and must be invoked 4081 // after the measure pass. If its invoked before the measure pass and the app modifies 4082 // the view hierarchy in the callbacks, we could leave the views in a broken state. 4083 if (surfaceCreated) { 4084 notifySurfaceCreated(mTransaction); 4085 didUseTransaction = true; 4086 } else if (surfaceReplaced) { 4087 notifySurfaceReplaced(mTransaction); 4088 didUseTransaction = true; 4089 } else if (surfaceDestroyed) { 4090 notifySurfaceDestroyed(); 4091 } 4092 4093 if (didUseTransaction) { 4094 applyTransactionOnDraw(mTransaction); 4095 } 4096 4097 if (triggerGlobalLayoutListener) { 4098 mAttachInfo.mRecomputeGlobalAttributes = false; 4099 mAttachInfo.mTreeObserver.dispatchOnGlobalLayout(); 4100 } 4101 4102 Rect contentInsets = null; 4103 Rect visibleInsets = null; 4104 Region touchableRegion = null; 4105 int touchableInsetMode = TOUCHABLE_INSETS_REGION; 4106 boolean computedInternalInsets = false; 4107 if (computesInternalInsets) { 4108 final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets; 4109 4110 // Clear the original insets. 4111 insets.reset(); 4112 4113 // Compute new insets in place. 4114 mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets); 4115 mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty(); 4116 4117 // Tell the window manager. 4118 if (insetsPending || !mLastGivenInsets.equals(insets)) { 4119 mLastGivenInsets.set(insets); 4120 4121 // Translate insets to screen coordinates if needed. 4122 if (mTranslator != null) { 4123 contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets); 4124 visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets); 4125 touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion); 4126 } else { 4127 contentInsets = insets.contentInsets; 4128 visibleInsets = insets.visibleInsets; 4129 touchableRegion = insets.touchableRegion; 4130 } 4131 computedInternalInsets = true; 4132 } 4133 touchableInsetMode = insets.mTouchableInsets; 4134 } 4135 boolean needsSetInsets = computedInternalInsets; 4136 needsSetInsets |= !Objects.equals(mPreviousTouchableRegion, mTouchableRegion) && 4137 (mTouchableRegion != null); 4138 if (needsSetInsets) { 4139 if (mTouchableRegion != null) { 4140 if (mPreviousTouchableRegion == null) { 4141 mPreviousTouchableRegion = new Region(); 4142 } 4143 mPreviousTouchableRegion.set(mTouchableRegion); 4144 if (touchableInsetMode != TOUCHABLE_INSETS_REGION) { 4145 Log.e(mTag, "Setting touchableInsetMode to non TOUCHABLE_INSETS_REGION" + 4146 " from OnComputeInternalInsets, while also using setTouchableRegion" + 4147 " causes setTouchableRegion to be ignored"); 4148 } 4149 } else { 4150 mPreviousTouchableRegion = null; 4151 } 4152 if (contentInsets == null) contentInsets = new Rect(0,0,0,0); 4153 if (visibleInsets == null) visibleInsets = new Rect(0,0,0,0); 4154 if (touchableRegion == null) { 4155 touchableRegion = mTouchableRegion; 4156 } else if (touchableRegion != null && mTouchableRegion != null) { 4157 touchableRegion.op(touchableRegion, mTouchableRegion, Region.Op.UNION); 4158 } 4159 try { 4160 mWindowSession.setInsets(mWindow, touchableInsetMode, 4161 contentInsets, visibleInsets, touchableRegion); 4162 } catch (RemoteException e) { 4163 throw e.rethrowFromSystemServer(); 4164 } 4165 } else if (mTouchableRegion == null && mPreviousTouchableRegion != null) { 4166 mPreviousTouchableRegion = null; 4167 try { 4168 mWindowSession.clearTouchableRegion(mWindow); 4169 } catch (RemoteException e) { 4170 throw e.rethrowFromSystemServer(); 4171 } 4172 } 4173 4174 if (mFirst) { 4175 if (sAlwaysAssignFocus || !isInTouchMode()) { 4176 // handle first focus request 4177 if (DEBUG_INPUT_RESIZE) { 4178 Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus()); 4179 } 4180 if (mView != null) { 4181 if (!mView.hasFocus()) { 4182 mView.restoreDefaultFocus(); 4183 if (DEBUG_INPUT_RESIZE) { 4184 Log.v(mTag, "First: requested focused view=" + mView.findFocus()); 4185 } 4186 } else { 4187 if (DEBUG_INPUT_RESIZE) { 4188 Log.v(mTag, "First: existing focused view=" + mView.findFocus()); 4189 } 4190 } 4191 } 4192 } else { 4193 // Some views (like ScrollView) won't hand focus to descendants that aren't within 4194 // their viewport. Before layout, there's a good change these views are size 0 4195 // which means no children can get focus. After layout, this view now has size, but 4196 // is not guaranteed to hand-off focus to a focusable child (specifically, the edge- 4197 // case where the child has a size prior to layout and thus won't trigger 4198 // focusableViewAvailable). 4199 View focused = mView.findFocus(); 4200 if (focused instanceof ViewGroup 4201 && ((ViewGroup) focused).getDescendantFocusability() 4202 == ViewGroup.FOCUS_AFTER_DESCENDANTS) { 4203 focused.restoreDefaultFocus(); 4204 } 4205 } 4206 //#ifdef OPLUS_EXTENSION_HOOK 4207 //Honzhu@ANDROID.VIEW, 2020/07/20, add for Optimize app startup speed 4208 mViewRootImplExt.checkIsFragmentAnimUI(); 4209 //#endif /* OPLUS_EXTENSION_HOOK */ 4210 } 4211 4212 final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible; 4213 if (changedVisibility) { 4214 maybeFireAccessibilityWindowStateChangedEvent(); 4215 } 4216 4217 mFirst = false; 4218 mWillDrawSoon = false; 4219 mNewSurfaceNeeded = false; 4220 mViewVisibility = viewVisibility; 4221 4222 final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible; 4223 mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes); 4224 4225 if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { 4226 reportNextDraw("first_relayout"); 4227 } 4228 4229 mCheckIfCanDraw = isSyncRequest || cancelDraw; 4230 4231 boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw(); 4232 boolean cancelAndRedraw = cancelDueToPreDrawListener 4233 || (cancelDraw && mDrewOnceForSync); 4234 //#ifdef OPLUS_EXTENSION_HOOK 4235 //Guofu.Yang@@ANDROID.WMS 2023/03/14,Add for fixed bug 5268134,5132324 4236 cancelAndRedraw = mViewRootImplExt.cancelAndRedraw(mTag,cancelAndRedraw,isViewVisible,mSyncBuffer); 4237 //#endif // OPLUS_EXTENSION_HOOK 4238 4239 if (!cancelAndRedraw) { 4240 // A sync was already requested before the WMS requested sync. This means we need to 4241 // sync the buffer, regardless if WMS wants to sync the buffer. 4242 if (mActiveSurfaceSyncGroup != null) { 4243 mSyncBuffer = true; 4244 } 4245 4246 createSyncIfNeeded(); 4247 notifyDrawStarted(isInWMSRequestedSync()); 4248 mDrewOnceForSync = true; 4249 4250 // If the active SSG is also requesting to sync a buffer, the following needs to happen 4251 // 1. Ensure we keep track of the number of active syncs to know when to disable RT 4252 // RT animations that conflict with syncing a buffer. 4253 // 2. Add a safeguard SSG to prevent multiple SSG that sync buffers from being submitted 4254 // out of order. 4255 if (mActiveSurfaceSyncGroup != null && mSyncBuffer) { 4256 updateSyncInProgressCount(mActiveSurfaceSyncGroup); 4257 safeguardOverlappingSyncs(mActiveSurfaceSyncGroup); 4258 } 4259 } 4260 // #ifdef OPLUS_FEATURE_EXTENSION_HOOKS 4261 // Bard.Zhang@Android.UIFramework 2023-10-30 Add for : debug 6429918 4262 else { 4263 Log.w(TAG, "cancelAndRedraw cancelDueToPreDrawListener " + cancelDueToPreDrawListener 4264 + " cancelDraw " + cancelDraw 4265 + " mDrewOnceForSync " + mDrewOnceForSync 4266 + " isSyncRequest " + isSyncRequest); 4267 } 4268 // #endif /* OPLUS_FEATURE_EXTENSION_HOOKS */ 4269 4270 if (!isViewVisible) { 4271 mLastPerformTraversalsSkipDrawReason = "view_not_visible"; 4272 if (mPendingTransitions != null && mPendingTransitions.size() > 0) { 4273 for (int i = 0; i < mPendingTransitions.size(); ++i) { 4274 mPendingTransitions.get(i).endChangingAnimations(); 4275 } 4276 mPendingTransitions.clear(); 4277 } 4278 4279 if (mActiveSurfaceSyncGroup != null) { 4280 mActiveSurfaceSyncGroup.markSyncReady(); 4281 } 4282 } else if (cancelAndRedraw) { 4283 mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener 4284 ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason() 4285 : "cancel_" + cancelReason; 4286 // Try again 4287 scheduleTraversals(); 4288 } else { 4289 if (mPendingTransitions != null && mPendingTransitions.size() > 0) { 4290 for (int i = 0; i < mPendingTransitions.size(); ++i) { 4291 mPendingTransitions.get(i).startChangingAnimations(); 4292 } 4293 mPendingTransitions.clear(); 4294 } 4295 if (!performDraw(mActiveSurfaceSyncGroup) && mActiveSurfaceSyncGroup != null) { 4296 mActiveSurfaceSyncGroup.markSyncReady(); 4297 } 4298 } 4299 4300 if (mAttachInfo.mContentCaptureEvents != null) { 4301 notifyContentCaptureEvents(); 4302 } 4303 mIsInTraversal = false; 4304 mRelayoutRequested = false; 4305 4306 if (!cancelAndRedraw) { 4307 mReportNextDraw = false; 4308 mLastReportNextDrawReason = null; 4309 mActiveSurfaceSyncGroup = null; 4310 mSyncBuffer = false; 4311 if (isInWMSRequestedSync()) { 4312 mWmsRequestSyncGroup.markSyncReady(); 4313 mWmsRequestSyncGroup = null; 4314 mWmsRequestSyncGroupState = WMS_SYNC_NONE; 4315 } 4316 } 4317 4318 // #ifdef OPLUS_FEATURE_VIEW_DEBUG 4319 // Bard.Zhang@Android.UIFramework 2023-01-28 Add for : view debug 4320 mViewRootImplExt.markOnPerformTraversalsEnd(host); 4321 // #endif /* OPLUS_FEATURE_VIEW_DEBUG */ 4322 4323 // #ifdef OPLUS_EXTENSION_HOOK 4324 // yanliang@Android.Performance 2023-02-13 Add for Optimize sliding effect 4325 mViewRootImplExt.checkPendingBufferCountSetting(mSurface); 4326 // #endif /*OPLUS_EXTENSION_HOOK*/ 4327 } 4328 解释里面的每行代码
09-24
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值