performDraw

  1. 初始化app后DecorView调用performTraversals(),执行完measure和layout后知道了view的ltrb(左上右下)坐标信息,下一步就是执行performDraw进行绘制了
  2. performDraw中经过层层调用,最终构造了canvas,并调用mView.draw(canvas)
  3. view的draw分了六步
        1. Draw the background
        2. If necessary, save the canvas' layers to prepare for fading(可忽略)
        3. Draw view's content
        4. Draw children
        5. If necessary, draw the fading edges and restore layers(可忽略)
        6. Draw decorations (scrollbars for instance)
    1. 画背景
      1. setBackgroundBounds中根据layout获得的ltrb参数设置绘制区域
    2. 保存canvas的layer信息
    3. 画view内容
      1. 空函数,需要具体类去实现
    4. 遍历画子view dispatchDraw(canvas);
      1. 没有子view不需要处理
      2. 看一下viewGroup中的处理
        protected void dispatchDraw(Canvas canvas) {
            boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
            final int childrenCount = mChildrenCount;
            final View[] children = mChildren;
            int flags = mGroupFlags;
        
            if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
                final boolean buildCache = !isHardwareAccelerated();
                for (int i = 0; i < childrenCount; i++) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                        final LayoutParams params = child.getLayoutParams();
                        attachLayoutAnimationParameters(child, params, i, childrenCount);
                        bindLayoutAnimation(child);
                    }
                }
        
                final LayoutAnimationController controller = mLayoutAnimationController;
                if (controller.willOverlap()) {
                    mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
                }
        
                controller.start();
        
                mGroupFlags &= ~FLAG_RUN_ANIMATION;
                mGroupFlags &= ~FLAG_ANIMATION_DONE;
        
                if (mAnimationListener != null) {
                    mAnimationListener.onAnimationStart(controller.getAnimation());
                }
            }
        
            int clipSaveCount = 0;
            final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
            if (clipToPadding) {
                clipSaveCount = canvas.save();
                canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                        mScrollX + mRight - mLeft - mPaddingRight,
                        mScrollY + mBottom - mTop - mPaddingBottom);
            }
        
            // We will draw our child's animation, let's reset the flag
            mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
            mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
        
            boolean more = false;
            final long drawingTime = getDrawingTime();
        
            if (usingRenderNodeProperties) canvas.insertReorderBarrier();
            final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
            int transientIndex = transientCount != 0 ? 0 : -1;
            // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
            // draw reordering internally
            final ArrayList<View> preorderedList = usingRenderNodeProperties
                    ? null : buildOrderedChildList();
            final boolean customOrder = preorderedList == null
                    && isChildrenDrawingOrderEnabled();
            for (int i = 0; i < childrenCount; i++) {
                while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                    final View transientChild = mTransientViews.get(transientIndex);
                    if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                            transientChild.getAnimation() != null) {
                        more |= drawChild(canvas, transientChild, drawingTime);
                    }
                    transientIndex++;
                    if (transientIndex >= transientCount) {
                        transientIndex = -1;
                    }
                }
        
                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
            while (transientIndex >= 0) {
                // there may be additional transient views after the normal views
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    break;
                }
            }
            if (preorderedList != null) preorderedList.clear();
        
            // Draw any disappearing views that have animations
            if (mDisappearingChildren != null) {
                final ArrayList<View> disappearingChildren = mDisappearingChildren;
                final int disappearingCount = disappearingChildren.size() - 1;
                // Go backwards -- we may delete as animations finish
                for (int i = disappearingCount; i >= 0; i--) {
                    final View child = disappearingChildren.get(i);
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
            if (usingRenderNodeProperties) canvas.insertInorderBarrier();
        
            if (debugDraw()) {
                onDebugDraw(canvas);
            }
        
            if (clipToPadding) {
                canvas.restoreToCount(clipSaveCount);
            }
        
            // mGroupFlags might have been updated by drawChild()
            flags = mGroupFlags;
        
            if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
                invalidate(true);
            }
        
            if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
                    mLayoutAnimationController.isDone() && !more) {
                // We want to erase the drawing cache and notify the listener after the
                // next frame is drawn because one extra invalidate() is caused by
                // drawChild() after the animation is over
                mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
                final Runnable end = new Runnable() {
                   @Override
                   public void run() {
                       notifyAnimationListener();
                   }
                };
                post(end);
            }
        }


      3. 遍历子view执行 drawChild(canvas, transientChild, drawingTime),drawChild里面执行了child.draw
    5. 画渐变的效果,并保存layrers
    6. 画scrollbars和foreground
----- pid 28257 at 2025-08-12 12:17:54.599346475+0800 ----- Cmd line: com.ss.android.article.video Build fingerprint: 'realme/RMX3992/RE5C3F:16/BP2A.250605.015/U.2bb19e6-cb8404-cbba88:user/release-keys' ABI: 'arm64' Build type: optimized Debug Store: 3,16,37768860::ID:0,C:P,T:37763452,N:Finish,D:tname=main;tid=2;prid=4715950||ID:19,C:E,T:37763452||ID:19,C:S,T:37763452,N:BcRcvReg,D:tname=main;tid=2;act=android.intent.action.SIM_STATE_CHANGED;cmp=null;pkg=null;prid=4715950||ID:0,C:P,T:37763452,N:Finish,D:tname=main;tid=2;prid=272134d||ID:18,C:E,T:37763452||ID:18,C:S,T:37763451,N:BcRcvReg,D:tname=main;tid=2;act=android.net.conn.CONNECTIVITY_CHANGE;cmp=null;pkg=null;prid=272134d||ID:0,C:P,T:37763450,N:Finish,D:tname=main;tid=2;prid=33a5b38||ID:17,C:E,T:37763450||ID:17,C:S,T:37763450,N:BcRcvReg,D:tname=main;tid=2;act=android.net.conn.CONNECTIVITY_CHANGE;cmp=null;pkg=null;prid=33a5b38||ID:0,C:P,T:37763446,N:Finish,D:tname=main;tid=2;prid=a07ff43||ID:16,C:E,T:37763446||ID:16,C:S,T:37763446,N:BcRcvReg,D:tname=main;tid=2;act=android.intent.action.BATTERY_CHANGED;cmp=null;pkg=null;prid=a07ff43||ID:0,C:P,T:37763444,N:Finish,D:tname=main;tid=2;prid=2bef1bc||ID:15,C:E,T:37763444||ID:15,C:S,T:37763443,N:BcRcvReg,D:tname=main;tid=2;act=android.net.conn.CONNECTIVITY_CHANGE;cmp=null;pkg=null;prid=2bef1bc||ID:0,C:P,T:37763443,N:Finish,D:tname=main;tid=2;prid=4010ab7;; suspend all histogram: Sum: 133us 99% C.I. 4us-33us Avg: 13.300us Max: 33us DALVIK THREADS (170): "main" prio=5 tid=1 Native | group="main" sCount=1 ucsCount=0 flags=1 obj=0xabe78430 self=0xb400007e828c2000 | sysTid=28257 nice=-10 cgrp=top-app sched=0/0 handle=0x7f3bae6098 | state=S schedstat=( 803321781 98899153 962 ) utm=61 stm=18 core=6 HZ=100 | stack=0x7ff8f1e000-0x7ff8f20000 stackSize=8188KB | held mutexes= native: #00 pc 000aeb1c /apex/com.android.runtime/lib64/bionic/libc.so (syscall+28) (BuildId: c135d9ca249ce7b7bdf2415d0639f65d) native: #01 pc 000897f0 /apex/com.android.runtime/lib64/bionic/libc.so (__futex_wait_ex+144) (BuildId: c135d9ca249ce7b7bdf2415d0639f65d) native: #02 pc 00097688 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_cond_wait+72) (BuildId: c135d9ca249ce7b7bdf2415d0639f65d) native: #03 pc 000a19ac /system/lib64/libc++.so (std::__1::__assoc_sub_state::copy+56) (BuildId: ada37e5198285720b02f7d77fd27626c7782fe29) native: #04 pc 000a1d14 /system/lib64/libc++.so (std::__1::future<void>::get+28) (BuildId: ada37e5198285720b02f7d77fd27626c7782fe29) native: #05 pc 0038ca1c /system/lib64/libhwui.so (android::uirenderer::renderthread::RenderProxy::setStopped+284) (BuildId: 42040d65e42b0c44e419563874d2eff0) at android.graphics.HardwareRenderer.nSetStopped(Native method) at android.graphics.HardwareRenderer.setStopped(HardwareRenderer.java:599) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:6317) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:5082) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:3508) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:11929) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1809) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1818) at android.view.Choreographer.doCallbacks(Choreographer.java:1336) at android.view.Choreographer.doFrame(Choreographer.java:1222) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1792) at android.os.Handler.handleCallback(Handler.java:1027) at android.os.Handler.dispatchMessage(Handler.java:108) at android.os.Looper.loopOnce(Looper.java:298) at android.os.Looper.loop(Looper.java:408) at android.app.ActivityThread.main(ActivityThread.java:9952) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:613) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1074) DumpLatencyMs: 0.55377分析这个ANR看是系统的问题还是应用的问题
最新发布
08-28
package com.example.kucun2.ui.dingdan;//package com.example.kucun2; import static android.content.ContentValues.TAG; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.AlertDialog; import android.content.Context; import android.graphics.Color; import android.graphics.Typeface; import android.os.Bundle; import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.SearchView; import android.widget.Spinner; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import com.example.kucun2.MainActivity; import com.example.kucun2.R; import com.example.kucun2.View.HorizontalScrollTextView; import com.example.kucun2.entity.Bancai; import com.example.kucun2.entity.Chanpin; import com.example.kucun2.entity.Chanpin_Zujian; import com.example.kucun2.entity.Dingdan; import com.example.kucun2.entity.Dingdan_chanpin_zujian; import com.example.kucun2.entity.Dingdan_Chanpin; import com.example.kucun2.entity.data.Data; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class OrderDisplayFragment extends Fragment { private TableLayout table; private HorizontalScrollView horizontalScrollView; private ValueAnimator scrollIndicatorAnimator; private boolean isIndicatorVisible = false; // 添加排序相关的成员变量 private int currentSortColumn = -1; private boolean sortAscending = true; private List<Object[]> allTableRowsData = new ArrayList<>(); // 添加搜索相关成员变量 private SearchView searchView; private Spinner columnSelector; private List<Object[]> filteredTableRowsData = new ArrayList<>(); private boolean isDataLoaded = false; /** * 加载初始化 * * @param inflater The LayoutInflater object that can be used to inflate * any views in the fragment, * @param container If non-null, this is the parent view that the fragment's * UI should be attached to. The fragment should not add the view itself, * but this can be used to generate the LayoutParams of the view. * @param savedInstanceState If non-null, this fragment is being re-constructed * from a previous saved state as given here. * @return */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_order_display, container, false); table = view.findViewById(R.id.orderTable); horizontalScrollView = view.findViewById(R.id.horizontalScrollContainer); View scrollIndicator = view.findViewById(R.id.scroll_indicator); // 获取搜索控件 searchView = view.findViewById(R.id.search_view); columnSelector = view.findViewById(R.id.column_selector); // 初始化表头选择器 initColumnSelector(); // 设置搜索监听 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { applySearchFilter(); return true; } @Override public boolean onQueryTextChange(String newText) { applySearchFilter(); return true; } }); LinearLayout fixedSearchBar = view.findViewById(R.id.fixedSearchBar); View placeholder = view.findViewById(R.id.search_bar_placeholder); // 添加全局布局监听器以获取正确的搜索框高度 fixedSearchBar.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // 获取搜索框的实际高度 int searchBarHeight = fixedSearchBar.getHeight(); // 设置占位视图的高度 ViewGroup.LayoutParams params = placeholder.getLayoutParams(); params.height = searchBarHeight; placeholder.setLayoutParams(params); // 确保仅运行一次 fixedSearchBar.getViewTreeObserver().removeOnGlobalLayoutListener(this); } } ); Log.d(TAG, "onCreateView: " + Data.dingdans.get(0).getNumber()); // 添加表头 addTableHeader(table); // 检查数据是否已加载 if (Data.dingdans.isEmpty()) { // 显示加载指示器 showLoadingIndicator(); // 设置数据加载监听器 if (getActivity() instanceof MainActivity) { ((MainActivity) getActivity()).setOnDataLoadListener(new MainActivity.OnDataLoadListener() { @Override public void onDataLoaded() { requireActivity().runOnUiThread(() -> { hideLoadingIndicator(); isDataLoaded = true; fillTableData(); // 填充数据 }); } @Override public void onDataError() { requireActivity().runOnUiThread(() -> { hideLoadingIndicator(); Toast.makeText(getContext(), "检查网络", Toast.LENGTH_SHORT).show(); //showError("数据加载失败"); }); } }); } } else { // 数据已加载,直接填充 fillTableData(); isDataLoaded = true; } // 填充表格数据 // fillTableData(); // 添加滚动监听 horizontalScrollView.getViewTreeObserver().addOnScrollChangedListener(() -> { int maxScroll = horizontalScrollView.getChildAt(0).getWidth() - horizontalScrollView.getWidth(); int currentScroll = horizontalScrollView.getScrollX(); if (currentScroll > 0 && maxScroll > 0) { if (!isIndicatorVisible) { showScrollIndicator(); } // 更新滚动指示器位置 updateScrollIndicatorPosition(currentScroll, maxScroll); } else { hideScrollIndicator(); } }); return view; } // 显示/隐藏加载指示器的方法 private void showLoadingIndicator() { // 实现加载动画或进度条 } private void hideLoadingIndicator() { // 隐藏加载指示器 } @Override public void onAttach(@NonNull Context context) { super.onAttach(context); if (context instanceof MainActivity) { ((MainActivity) context).setOnDataLoadListener(new MainActivity.OnDataLoadListener() { @Override public void onDataLoaded() { // 数据加载完成后填充表格 getActivity().runOnUiThread(() -> { Log.d("DataLoad", "Data loaded, filling table"); fillTableData(); }); } @Override public void onDataError() { //showToast("数据加载失败"); } }); } } /** * 获取数据 */ private void fillTableData() { List<Dingdan> orders = Data.dingdans; List<Dingdan_Chanpin> orderProducts = Data.dingdan_chanpins; List<Dingdan_chanpin_zujian> orderMaterials = Data.Dingdan_chanpin_zujians; allTableRowsData.clear(); filteredTableRowsData.clear(); // 创建映射关系提高效率 Map<Integer, List<Dingdan_Chanpin>> orderProductMap = new HashMap<>(); Map<Integer, List<Chanpin_Zujian>> productComponentMap = new HashMap<>(); Map<Integer, List<Dingdan_chanpin_zujian>> componentMaterialMap = new HashMap<>(); // 构建映射 for (Dingdan_Chanpin op : orderProducts) { if (op != null && op.getDingdan() != null) { int orderId = op.getDingdan().getId(); orderProductMap.computeIfAbsent(orderId, k -> new ArrayList<>()).add(op); } } for (Chanpin_Zujian cz : Data.chanpin_zujians) { int productId = cz.getChanpin().getId(); productComponentMap.computeIfAbsent(productId, k -> new ArrayList<>()).add(cz); } for (Dingdan_chanpin_zujian dm : orderMaterials) { int componentId = dm.getZujian().getId(); componentMaterialMap.computeIfAbsent(componentId, k -> new ArrayList<>()).add(dm); } // 重组数据 for (Dingdan order : orders) { List<Dingdan_Chanpin> productsForOrder = orderProductMap.get(order.getId()); if (productsForOrder != null) { for (Dingdan_Chanpin op : productsForOrder) { Chanpin product = op.getChanpin(); List<Chanpin_Zujian> componentsForProduct = productComponentMap.get(product.getId()); if (componentsForProduct != null) { for (Chanpin_Zujian cz : componentsForProduct) { List<Dingdan_chanpin_zujian> materialsForComponent = componentMaterialMap.get(cz.getZujian().getId()); if (materialsForComponent != null) { for (Dingdan_chanpin_zujian dm : materialsForComponent) { Object[] rowData = createRowData(order, product, op, cz, dm); allTableRowsData.add(rowData); filteredTableRowsData.add(rowData); } } } } } } } // 日志记录添加行数 Log.d("TableFill", "Total rows created: " + allTableRowsData.size()); // 初始排序 sortTableData(-1, true); } /** * 排序表格数据并刷新显示 * * @param columnIndex 要排序的列索引 * @param ascending 是否升序排列 */ private void sortTableData(int columnIndex, boolean ascending) { // 更新排序状态 if (columnIndex >= 0) { if (currentSortColumn == columnIndex) { // 相同列点击时切换排序方向 sortAscending = !ascending; } else { currentSortColumn = columnIndex; sortAscending = true; // 新列默认升序 } } // 创建排序比较器 Comparator<Object[]> comparator = (row1, row2) -> { if (currentSortColumn < 0) { return 0; // 返回0表示相等,保持原顺序 } Object value1 = row1[currentSortColumn]; Object value2 = row2[currentSortColumn]; if (value1 == null && value2 == null) return 0; if (value1 == null) return -1; if (value2 == null) return 1; // 根据不同列数据类型定制比较规则 try { // 数值列:2(数量), 5(板材/组件), 6(订购数量) if (currentSortColumn == 2 || currentSortColumn == 5 || currentSortColumn == 6) { double d1 = Double.parseDouble(value1.toString()); double d2 = Double.parseDouble(value2.toString()); return sortAscending ? Double.compare(d1, d2) : Double.compare(d2, d1); } // 其他列按字符串排序 else { String s1 = value1.toString().toLowerCase(); String s2 = value2.toString().toLowerCase(); return sortAscending ? s1.compareTo(s2) : s2.compareTo(s1); } } catch (NumberFormatException e) { // 解析失败时按字符串比较 String s1 = value1.toString().toLowerCase(); String s2 = value2.toString().toLowerCase(); return sortAscending ? s1.compareTo(s2) : s2.compareTo(s1); } }; // 特殊处理初始未排序状态 if (columnIndex == -1) { // 直接复制数据而不排序 filteredTableRowsData.clear(); filteredTableRowsData.addAll(allTableRowsData); } else { Collections.sort(filteredTableRowsData, comparator); } // 刷新显示 refreshTableWithData(filteredTableRowsData); } /** * 表格数据动态添加 * * @param rowData */ private void addTableRow(Object[] rowData) { TableRow row = new TableRow(requireContext()); TableLayout.LayoutParams rowParams = new TableLayout.LayoutParams( TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.WRAP_CONTENT ); row.setLayoutParams(rowParams); row.setMinimumHeight(dpToPx(36)); for (int i = 0; i < rowData.length; i++) { final Object data = rowData[i]; // 判断是否为操作列(最后一列) if (i == rowData.length - 1) { // 创建操作按钮 Button actionButton = new Button(requireContext()); actionButton.setText("操作"); actionButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); actionButton.setBackgroundResource(R.drawable.btn_selector); // 自定义按钮样式 // 设置按钮点击监听器 actionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { handleRowAction(rowData, v); } }); // 设置按钮布局参数 TableRow.LayoutParams btnParams = new TableRow.LayoutParams( 0, // 宽度由权重控制 TableRow.LayoutParams.WRAP_CONTENT, 0.5f ); btnParams.weight = 0.5f; int margin = dpToPx(1); btnParams.setMargins(margin, margin, margin, margin); actionButton.setLayoutParams(btnParams); actionButton.setHeight(11); row.addView(actionButton); } else { // 正常文本列的代码(保持原逻辑) HorizontalScrollTextView textView = new HorizontalScrollTextView(requireContext()); textView.setText(String.valueOf(data)); textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); int padding = dpToPx(8); textView.setPadding(padding, padding / 2, padding, padding); textView.setMinWidth(dpToPx(50)); TableRow.LayoutParams colParams = null; // 设置背景边框 textView.setBackgroundResource(R.drawable.cell_border); if (data.toString().length() > 10) { colParams = new TableRow.LayoutParams( 0, // 宽度将由权重控制 TableRow.LayoutParams.MATCH_PARENT, 2.0f ); colParams.weight = 2; } else { colParams = new TableRow.LayoutParams( 0, // 宽度将由权重控制 TableRow.LayoutParams.MATCH_PARENT, 1.0f ); colParams.weight = 1; } textView.setLayoutParams(colParams); row.addView(textView); } } table.addView(row); } // 动态添加表头 (使用自定义TextView) private void addTableHeader(TableLayout table) { TableRow headerRow = new TableRow(requireContext()); headerRow.setLayoutParams(new TableLayout.LayoutParams( TableLayout.LayoutParams.MATCH_PARENT, TableLayout.LayoutParams.WRAP_CONTENT )); // 设置行背景颜色 headerRow.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.purple_500)); // 定义表头 // 更新表头数组(添加操作列) String[] headers = getResources().getStringArray(R.array.table_headers); List<String> headerList = new ArrayList<>(Arrays.asList(headers)); headerList.add("操作"); // 添加操作列标题 headers = headerList.toArray(new String[0]); // 更新权重数组(添加操作列权重) float[] weights = {1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 0.5f}; // 新增操作列权重0.5 // 更新优先级数组(添加操作列优先级) boolean[] priority = {false, false, false, false, true, false, false, false}; for (int i = 0; i < headers.length; i++) { HorizontalScrollTextView headerView = new HorizontalScrollTextView(requireContext()); headerView.setText(headers[i]); headerView.setTextColor(Color.WHITE); headerView.setTypeface(null, Typeface.BOLD); headerView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); headerView.setPadding(dpToPx(8), dpToPx(8), dpToPx(8), dpToPx(8)); // 为优先级高的列设置最小宽度 if (priority[i]) { headerView.setMinWidth(dpToPx(220)); } // 设置布局参数 TableRow.LayoutParams colParams = new TableRow.LayoutParams( priority[i] ? TableRow.LayoutParams.WRAP_CONTENT : 0, TableRow.LayoutParams.MATCH_PARENT, priority[i] ? 0 : weights[i] // 优先级列不使用权重 ); headerView.setLayoutParams(colParams); final int columnIndex = i; headerView.setOnClickListener(v -> { // 排序并刷新表格 sortTableData(columnIndex, sortAscending); // 更新排序指示器(可选) showSortIndicator(headerView); }); headerRow.addView(headerView); } table.addView(headerRow); } // 添加排序指示器(可选) private void showSortIndicator(View header) { // 实现:在表头右侧添加↑或↓指示符 // 实现逻辑根据设计需求 // header.setTooltipText(new ); } /** * */ private void showScrollIndicator() { isIndicatorVisible = true; View indicator = getView().findViewById(R.id.scroll_indicator); if (scrollIndicatorAnimator != null && scrollIndicatorAnimator.isRunning()) { scrollIndicatorAnimator.cancel(); } indicator.setVisibility(View.VISIBLE); indicator.setAlpha(0f); scrollIndicatorAnimator = ObjectAnimator.ofFloat(indicator, "alpha", 0f, 0.8f); scrollIndicatorAnimator.setDuration(300); scrollIndicatorAnimator.start(); } /** * + */ private void hideScrollIndicator() { isIndicatorVisible = false; View indicator = getView().findViewById(R.id.scroll_indicator); if (scrollIndicatorAnimator != null && scrollIndicatorAnimator.isRunning()) { scrollIndicatorAnimator.cancel(); } scrollIndicatorAnimator = ObjectAnimator.ofFloat(indicator, "alpha", indicator.getAlpha(), 0f); scrollIndicatorAnimator.setDuration(300); scrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { indicator.setVisibility(View.INVISIBLE); } }); scrollIndicatorAnimator.start(); } /** * @param currentScroll * @param maxScroll */ private void updateScrollIndicatorPosition(int currentScroll, int maxScroll) { View indicator = getView().findViewById(R.id.scroll_indicator); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) indicator.getLayoutParams(); // 计算指示器位置(0-100%) float percentage = (float) currentScroll / maxScroll; int maxMargin = getResources().getDisplayMetrics().widthPixels - indicator.getWidth(); // 设置右边距(控制位置) params.rightMargin = (int) (maxMargin * percentage); indicator.setLayoutParams(params); } // 处理行操作的方法 private void handleRowAction(Object[] rowData, View anchorButton) { // 安全地从行数据中提取关键信息 String orderNumber = safeGetString(rowData[0]); // 订单号 String productId = safeGetString(rowData[1]); // 产品ID String componentName = safeGetString(rowData[3]); // 组件名称 // 安全地获取订购数量 double materialQuantity = 0.0; try { if (rowData[6] != null) { if (rowData[6] instanceof Number) { materialQuantity = ((Number) rowData[6]).doubleValue(); } else { materialQuantity = Double.parseDouble(rowData[6].toString()); } } } catch (Exception e) { Log.e("OrderFragment", "Failed to parse material quantity", e); } Context context = getContext(); if (context == null || anchorButton == null) { Log.w("PopupMenu", "Context or anchorButton is null"); return; } PopupMenu popupMenu = new PopupMenu(context, anchorButton); // 强制设置菜单在锚点视图下方显示(关键设置) popupMenu.setGravity(Gravity.BOTTOM); // 如果使用支持库,设置弹出方向 // 设置在锚点视图下方显示 // popupMenu.setOverlapAnchor(true); // 填充菜单项 popupMenu.getMenuInflater().inflate(R.menu.row_actions_menu, popupMenu.getMenu()); // 设置菜单项点击监听器 popupMenu.setOnMenuItemClickListener(item -> { int itemId = item.getItemId(); if (itemId == R.id.action_view_details) { showDetailDialog(orderNumber, productId); return true; } else if (itemId == R.id.action_edit) { editRowData(rowData); return true; } else if (itemId == R.id.action_delete) { deleteRowWithConfirm(rowData); return true; } return false; }); popupMenu.show(); } // 安全获取字符串值的方法 private String safeGetString(Object value) { if (value == null) return ""; if (value instanceof String) return (String) value; return value.toString(); } // 查看详情对话框 private void showDetailDialog(String orderNumber, String productId) { AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); builder.setTitle("订单详情") .setMessage("订单号: " + orderNumber + "\n产品ID: " + productId) .setPositiveButton("确定", null) .show(); } // 编辑行数据 private void editRowData(Object[] rowData) { // 实现编辑逻辑 // 这里创建包含表单的对话框 Toast.makeText(requireContext(), "编辑操作: " + rowData[0], Toast.LENGTH_SHORT).show(); } // 带确认的删除操作 private void deleteRowWithConfirm(Object[] rowData) { new AlertDialog.Builder(requireContext()) .setTitle("确认删除") .setMessage("确定要删除订单 " + rowData[0] + " 吗?") .setPositiveButton("删除", (dialog, which) -> { // 实际删除逻辑 deleteRow(rowData); }) .setNegativeButton("取消", null) .show(); } // 实际删除行数据 private void deleteRow(Object[] rowData) { // 1. 从allTableRowsData中移除对应行 for (Iterator<Object[]> iterator = allTableRowsData.iterator(); iterator.hasNext(); ) { Object[] row = iterator.next(); if (Arrays.equals(row, rowData)) { iterator.remove(); break; } } // 2. 从filteredTableRowsData中移除 filteredTableRowsData.removeIf(row -> Arrays.equals(row, rowData)); // 3. 刷新表格 refreshTableWithData(filteredTableRowsData); Toast.makeText(requireContext(), "已删除订单", Toast.LENGTH_SHORT).show(); } // DP转PX工具方法 private int dpToPx(int dp) { return (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics() ); } /** * 数据组合 * * @param order * @param product * @param component * @param material * @return */ private Object[] createRowData(Dingdan order, Chanpin product, Dingdan_Chanpin dingdan_chanpin, Chanpin_Zujian component, Dingdan_chanpin_zujian material) { Bancai board = material.getBancai(); String boardInfo = board.TableText(); ; return new Object[]{ order.getNumber(), // 订单号 product.getBianhao(), // 产品编号 dingdan_chanpin.getShuliang(), // 产品数量 (根据需求调整) component.getZujian().getName(), // 组件名 boardInfo, // 板材信息 Math.round(component.getOne_howmany()), // 板材/组件 material.getShuliang(), // 订购数量 "操作" }; } // 初始化列选择器 private void initColumnSelector() { ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource( requireContext(), R.array.table_headers, android.R.layout.simple_spinner_item ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); columnSelector.setAdapter(adapter); // 添加"所有列"选项 columnSelector.setSelection(0); // 默认选择第一个选项(所有列) // 列选择变化监听 columnSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { applySearchFilter(); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); } // 应用搜索过滤 private void applySearchFilter() { String query = searchView.getQuery().toString().trim().toLowerCase(); int selectedColumn = columnSelector.getSelectedItemPosition(); filteredTableRowsData.clear(); if (query.isEmpty()) { // 没有搜索词,显示所有数据 filteredTableRowsData.addAll(allTableRowsData); } else { // 根据选择的列进行过滤 for (Object[] row : allTableRowsData) { // 如果选择"所有列"(位置0),检查所有列 if (selectedColumn == 0) { for (Object cell : row) { if (cell != null && cell.toString().toLowerCase().contains(query)) { filteredTableRowsData.add(row); break; } } } // 检查特定列 else if (selectedColumn >= 1 && selectedColumn <= row.length) { int columnIndex = selectedColumn - 1; // 调整索引(0=所有列,1=第一列) if (row[columnIndex] != null && row[columnIndex].toString().toLowerCase().contains(query)) { filteredTableRowsData.add(row); } } } } // 刷新表格显示 refreshTableWithData(filteredTableRowsData); } /** * 刷新表格显示 */ private void refreshTableWithData(Iterable<? extends Object[]> dataToShow) { // Log.d("TableRefresh", "Refreshing table with " + currentSortColumn + " rows"); // 添加调试信息 Log.d("TableRefresh", "Refreshing table with " + currentSortColumn + " rows"); removeAllRowsSafely(); int addedRows = 0; for (Object[] rowData : dataToShow) { addTableRow(rowData); addedRows++; } // 添加空数据提示 if (addedRows == 0) { addEmptyTableRow(); } } private void addEmptyTableRow() { TableRow row = new TableRow(requireContext()); TextView emptyView = new TextView(requireContext()); emptyView.setText("暂无数据"); emptyView.setGravity(Gravity.CENTER); emptyView.setLayoutParams(new TableRow.LayoutParams( TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT )); row.addView(emptyView); table.addView(row); } private void removeAllRowsSafely() { // 移除除表头外的所有行(如果有表头) if (table.getChildCount() > 0) { // 保留表头(索引0) for (int i = table.getChildCount() - 1; i >= 1; i--) { View child = table.getChildAt(i); table.removeView(child); // 清理视图引用(非常重要!) cleanupRowViews((TableRow) child); } } } private void cleanupRowViews(TableRow row) { int childCount = row.getChildCount(); for (int i = 0; i < childCount; i++) { View view = row.getChildAt(i); // 解除视图的所有监听器 view.setOnClickListener(null); // 特别是操作按钮,需要取消所有监听器 if (view instanceof Button) { Button button = (Button) view; button.setOnClickListener(null); // 清空按钮的数据引用 button.setTag(null); } } // 从父视图中移除行 if (row.getParent() != null) { ((ViewGroup) row.getParent()).removeView(row); } } } E FATAL EXCEPTION: main Process: com.example.kucun2, PID: 2475 java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.View.findViewById(int)' on a null object reference at com.example.kucun2.ui.dingdan.OrderDisplayFragment.hideScrollIndicator(OrderDisplayFragment.java:553) at com.example.kucun2.ui.dingdan.OrderDisplayFragment.lambda$onCreateView$0(OrderDisplayFragment.java:199) at com.example.kucun2.ui.dingdan.OrderDisplayFragment.$r8$lambda$VEviHEc8N_lTbpxv9eW9n-N0BE8(Unknown Source:0) at com.example.kucun2.ui.dingdan.OrderDisplayFragment$$ExternalSyntheticLambda8.onScrollChanged(D8$$SyntheticClass:0) at android.view.ViewTreeObserver.dispatchOnScrollChanged(ViewTreeObserver.java:1301) at android.view.ViewRootImpl.draw(ViewRootImpl.java:6618) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:6390) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:5268) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:3667) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:12113) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:2459) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:2468) at android.view.Choreographer.doCallbacks(Choreographer.java:1693) at android.view.Choreographer.doFrame(Choreographer.java:1448) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:2284) at android.os.Handler.handleCallback(Handler.java:1014) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loopOnce(Looper.java:250) at android.os.Looper.loop(Looper.java:340) at android.app.ActivityThread.main(ActivityThread.java:9913) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:621) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:957)
06-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值