ValueAnimator设置时长无效

本文解决了一个问题,在某些安卓设备上,属性动画会异常地瞬间从起点跳到终点。通过调试发现这是由于系统动画时长缩放设置导致的问题,并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

转载:https://blog.youkuaiyun.com/u011387817/article/details/78628956

今天在低端机上面测试,发现里面的属性动画竟然无法正常播放, 一下子就从起点跳到终点。

一番调试无果后,就感觉是手机本身问题, 于是就想看看其他应用的属性动画在这台手机上是否也这样:

记得之开布局边界时,看到 网易云音乐客户端 的音乐播放界面中的光盘旋转效果是属性动画,于是打开应用后,随便播放一首歌,发现光盘果然是不转了,这时忽然想到 设置-开发者选项中有几个动画时长缩放的选项,打开一看,原来是 “动画程序时长缩放” 这个选项选择了关闭动画,重新打开后果然恢复正常了。

但是实际应用中总不能提示用户手动开启吧, 究竟设置中的这个选项是怎样影响到我们应用中的动画时长的呢。

还是先说解决方法吧:(经测试,每次在activity recreate后都会重置动画时长为系统指定的值, 哈哈,所以如果想偷懒,只在Application onCreate里面调用下面的方法的话,依然是不起作用的,建议每次播放动画前重置一下。)

public class ValueAnimatorUtil {

    /**
     * 如果动画被禁用,则重置动画缩放时长
     */
    public static void resetDurationScaleIfDisable() {
        if (getDurationScale() == 0)
            resetDurationScale();
    }

    /**
     * 重置动画缩放时长
     */
    public static void resetDurationScale() {
        try {
            getField().setFloat(null, 1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static float getDurationScale() {
        try {
            return getField().getFloat(null);
        } catch (Exception e) {
            e.printStackTrace();
            return -1;
        }
    }

    @NonNull
    private static Field getField() throws NoSuchFieldException {
        Field field = ValueAnimator.class.getDeclaredField("sDurationScale");
        field.setAccessible(true);
        return field;
    }
}

下面说说原因: 
打开ValueAnimator源码,看到一个私有的静态变量 sDurationScale:

public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
    private static final String TAG = "ValueAnimator";
    private static final boolean DEBUG = false;

    /**
     * Internal constants
     */
    private static float sDurationScale = 1.0f;

难道是这个东西在作怪?看看下面的方法: 
这个方法是处理动画帧的方法, 更新了mLastFrameTime后, 判断动画是否已经播放完毕, 
如果播放完毕,就进行结束动画的一系列处理: 回调接口、重置状态等

  /**
     * Processes a frame of the animation, adjusting the start time if needed.
     *
     * @param frameTime The frame time.
     * @return true if the animation has ended.
     * @hide
     */
    public final boolean doAnimationFrame(long frameTime) {
       ....
       ....

       mLastFrameTime = frameTime;
       final long currentTime = Math.max(frameTime, mStartTime);

       boolean finished = animateBasedOnTime(currentTime);
       if (finished) {
       endAnimation();
       } 
       return finished;
    }

我们来看看他是怎样判断动画是否播放完毕的: 
这次我们要关注的是getScaledDuration这个方法, 因为这个方法返回的值能决定是否播放完毕

    boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            final long scaledDuration = getScaledDuration();
            final float fraction = scaledDuration > 0 ?
                    (float)(currentTime - mStartTime) / scaledDuration : 1f;
            final float lastFraction = mOverallFraction;
            final boolean newIteration = (int) fraction > (int) lastFraction;
            final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                    (mRepeatCount != INFINITE);
            if (scaledDuration == 0) {
                // 0 duration animator, ignore the repeat count and skip to the end
                done = true;
            } else if (newIteration && !lastIterationFinished) {
                // Time to repeat
                if (mListeners != null) {
                    int numListeners = mListeners.size();
                    for (int i = 0; i < numListeners; ++i) {
                        mListeners.get(i).onAnimationRepeat(this);
                    }
                }
            } else if (lastIterationFinished) {
                done = true;
            }
            mOverallFraction = clampFraction(fraction);
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            animateValue(currentIterationFraction);
        }
        return done;
    }

getScaledDuration方法: 
是用我们设定的动画时长 * mDurationScale, 如果mDurationScale为0, 那么就满足上面的条件,直接认为动画播放完毕了

 private long getScaledDuration() {
        return (long)(mDuration * sDurationScale);
    }

下面我们来验证一下 设置-开发者选项-动画时长缩放 这个选项是否跟mDurationScale有关:

      try {
            Field field = ValueAnimator.class.getDeclaredField("sDurationScale");
            field.setAccessible(true);
            LogUtil.print(field.getFloat(null));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

先是默认的: 
这里写图片描述 
日志输出:

1.0
  •  

再试试5x的: 
这里写图片描述 
日志输出:

5.0
  • 1

没错了,最后试试关闭动画: 
这里写图片描述 
日志输出果然是0.0。 我们再播放一个ValueAnimator来测试下:

     ValueAnimator animator = ValueAnimator.ofInt(0, 100).setDuration(1000);
     animator.addUpdateListener(animation -> LogUtil.print(animation.getAnimatedValue()));
     animator.start();
  • 1
  • 2
  • 3

输出:

11-24 21:22:59.925 9244-9244/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 0
11-24 21:23:00.087 9244-9244/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 100
  • 1
  • 2

输出0之后,直接是100了,这就对应了开头描述的情况:一下子就从起点跳到终点。

最后我们试试在动画开始前重置mDurationScale:

     ValueAnimator animator = ValueAnimator.ofInt(0, 100).setDuration(1000);
      animator.addUpdateListener(animation -> LogUtil.print(animation.getAnimatedValue()));
      ValueAnimatorUtil.resetDurationScale();
      animator.start();

输出:

11-24 21:30:35.102 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 0
11-24 21:30:35.261 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 0
11-24 21:30:35.332 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 1
11-24 21:30:35.393 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 5
11-24 21:30:35.406 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 6
11-24 21:30:35.424 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 7
11-24 21:30:35.442 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 9
11-24 21:30:35.460 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 11
11-24 21:30:35.478 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 13
11-24 21:30:35.496 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 14
11-24 21:30:35.515 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 17
11-24 21:30:35.534 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 19
11-24 21:30:35.551 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 21
11-24 21:30:35.569 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 24
11-24 21:30:35.587 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 26
11-24 21:30:35.605 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 28
11-24 21:30:35.623 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 31
11-24 21:30:35.641 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 34
11-24 21:30:35.659 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 36
11-24 21:30:35.677 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 39
11-24 21:30:35.696 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 42
11-24 21:30:35.713 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 45
11-24 21:30:35.732 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 48
11-24 21:30:35.750 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 51
11-24 21:30:35.768 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 53
11-24 21:30:35.786 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 56
11-24 21:30:35.804 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 59
11-24 21:30:35.822 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 62
11-24 21:30:35.840 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 65
11-24 21:30:35.858 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 67
11-24 21:30:35.877 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 70
11-24 21:30:35.895 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 72
11-24 21:30:35.913 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 75
11-24 21:30:35.931 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 77
11-24 21:30:35.951 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 80
11-24 21:30:35.969 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 82
11-24 21:30:35.987 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 84
11-24 21:30:36.004 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 86
11-24 21:30:36.022 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 88
11-24 21:30:36.040 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 90
11-24 21:30:36.058 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 91
11-24 21:30:36.076 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 93
11-24 21:30:36.095 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 94
11-24 21:30:36.112 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 95
11-24 21:30:36.130 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 96
11-24 21:30:36.148 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 97
11-24 21:30:36.167 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 98
11-24 21:30:36.185 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 99
11-24 21:30:36.203 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 99
11-24 21:30:36.221 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 99
11-24 21:30:36.239 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 99
11-24 21:30:36.257 12224-12224/com.test E/com.test.TestActivity-->lambda$onCreate$0$TestActivity: 

 

package com.example.kucun2.ui.dingdan;//package com.example.kucun2; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.graphics.Color; import android.graphics.Typeface; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; 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_Bancai; import com.example.kucun2.entity.Dingdan_Chanpin; import com.example.kucun2.entity.data.Data; import com.example.kucun2.entity.Zujian; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; 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<>(); /** *加载初始化 * @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); // 添加表头 addTableHeader(table); // 填充表格数据 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 fillTableData() { List<Dingdan> orders = Data.dingdans; List<Dingdan_Chanpin> orderProducts = Data.dingdanChanpins; List<Dingdan_Bancai> orderMaterials = Data.dingdanBancais; for (Dingdan order : orders) { for (Dingdan_Chanpin orderProduct : orderProducts) { if (orderProduct.getDingdan().getId().equals(order.getId())) { Chanpin product = orderProduct.getChanpin(); for (Chanpin_Zujian component : product.getZujians()) { for (Dingdan_Bancai material : orderMaterials) { // 创建行数据但不立即添加到表格 Object[] rowData = createRowData( order, product, component, material ); allTableRowsData.add(rowData); if (material.getZujian() != null && material.getZujian().getId().equals(component.getId())) { addTableRow(createRowData( order, product, component, material )); } } } } } } // 初始排序 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) -> { 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); } }; // 排序数据 Collections.sort(allTableRowsData, comparator); // 刷新表格显示 refreshTable(); } /** * 刷新表格显示 */ private void refreshTable() { // 移除除表头外的所有行 int childCount = table.getChildCount(); if (childCount > 1) { table.removeViews(1, childCount - 1); } // 添加排序后的行 for (Object[] rowData : allTableRowsData) { addTableRow(rowData); } } /** * 表格数据动态添加 * @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 (Object data : rowData) { 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 / 2); 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); float[] weights = {1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 1.0f, 1.0f}; // 列宽优先级数组(板材信息列优先) boolean[] priority = {false, false, false, false, true, 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(200)); } // 设置布局参数 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(); } 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); } // 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, Chanpin_Zujian component, Dingdan_Bancai material) { Bancai board = material.getBancai(); String boardInfo = board.TableText(); ; return new Object[] { order.getNumber(), // 订单号 product.getId(), // 产品编号 "1", // 产品数量 (根据需求调整) component.getZujian().getName(), // 组件名 boardInfo, // 板材信息 Math.round(component.getOne_several()), // 板材/组件 material.getShuliang() // 订购数量 }; } }package com.example.kucun2.View; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.GradientDrawable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.ActionMode; import android.view.GestureDetector; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import com.example.kucun2.R; // HorizontalScrollTextView.java public class HorizontalScrollTextView extends androidx.appcompat.widget.AppCompatTextView { private GestureDetector gestureDetector; private ValueAnimator scrollAnimator; private int maxScrollX = 0; public HorizontalScrollTextView(Context context) { super(context); init(); } public HorizontalScrollTextView(Context context, AttributeSet attrs) { super(context, attrs); init(); loadAttributes(context, attrs); } public HorizontalScrollTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { GradientDrawable border = new GradientDrawable(); setSingleLine(); setEllipsize(TextUtils.TruncateAt.MARQUEE); setMarqueeRepeatLimit(-1); setHorizontallyScrolling(true); border.setStroke(2, Color.parseColor("#FF4081")); border.setCornerRadius(16); border.setColor(Color.TRANSPARENT); setBackground(border); // 确保禁用省略号和允许多行显示 // 创建手势检测器 gestureDetector = new GestureDetector(getContext(), new GestureListener()); // 设置文本可选中 setTextIsSelectable(false); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 确保视图至少达到最小宽度 int newWidthMeasureSpec = widthMeasureSpec; if (minWidthPx > 0) { int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); if (measuredWidth < minWidthPx) { newWidthMeasureSpec = MeasureSpec.makeMeasureSpec( minWidthPx, MeasureSpec.EXACTLY ); } } super.onMeasure(newWidthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); maxScrollX = computeHorizontalScrollRange() - getWidth(); } @Override public boolean onTouchEvent(MotionEvent event) { gestureDetector.onTouchEvent(event); return super.onTouchEvent(event); } public void smoothScrollToPosition(int scrollX) { if (scrollAnimator != null && scrollAnimator.isRunning()) { scrollAnimator.cancel(); } scrollAnimator = ValueAnimator.ofInt(getScrollX(), scrollX); scrollAnimator.setDuration(300); scrollAnimator.addUpdateListener(animation -> { setScrollX((Integer) animation.getAnimatedValue()); }); scrollAnimator.start(); } private class GestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { int newScrollX = getScrollX() + (int) distanceX; newScrollX = Math.max(0, Math.min(maxScrollX, newScrollX)); setScrollX(newScrollX); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // 实现投掷效果 int scrollX = getScrollX(); int targetScrollX = scrollX - (int) (velocityX / 10); targetScrollX = Math.max(0, Math.min(maxScrollX, targetScrollX)); smoothScrollToPosition(targetScrollX); return true; } @Override public boolean onDown(MotionEvent e) { return true; } } private int minWidthPx = 0; private void loadAttributes(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MinWidthScrollTextView); minWidthPx = ta.getDimensionPixelSize(R.styleable.MinWidthScrollTextView_minWidth, 0); ta.recycle(); } public void setMinWidthPx(int minWidthPx) { this.minWidthPx = minWidthPx; requestLayout(); } @Override public boolean isFocused() { return false; // 确保滚动效果 } } E FATAL EXCEPTION: main Process: com.example.kucun2, PID: 30966 java.lang.ArrayIndexOutOfBoundsException: length=7; index=-1 at com.example.kucun2.ui.dingdan.OrderDisplayFragment.lambda$sortTableData$1(OrderDisplayFragment.java:159) at com.example.kucun2.ui.dingdan.OrderDisplayFragment.$r8$lambda$O_ZN8yA5XHGAtSc_Cu3F-4YtUxM(Unknown Source:0) at com.example.kucun2.ui.dingdan.OrderDisplayFragment$$ExternalSyntheticLambda0.compare(D8$$SyntheticClass:0) at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) at java.util.TimSort.sort(TimSort.java:234) at java.util.Arrays.sort(Arrays.java:1351) at java.util.ArrayList.sort(ArrayList.java:1821)
最新发布
06-08
<think>我们遇到的问题是:在排序时发生数组越界异常,错误信息是`java.lang.ArrayIndexOutOfBoundsException:length=7;index=-1`。错误发生在`OrderDisplayFragment`类的`sortTableData`方法中的lambda表达式(比较器)内。分析原因:在`sortTableData`方法中,我们使用了一个比较器,该比较器尝试访问`row1[currentSortColumn]`和`row2[currentSortColumn]`。但是,错误信息显示,我们试图访问索引为-1的数组元素(因为数组长度是7,索引范围是0到6)。观察代码,在`sortTableData`方法中,我们调用了`sortTableData(-1,true)`作为初始排序。而在比较器中,我们直接使用`currentSortColumn`作为索引,当`currentSortColumn`为-1时,就会导致访问row1[-1],从而越界。解决方案:在比较器中,我们需要检查`currentSortColumn`是否有效(即是否在0到6之间)。如果无效,我们可以返回0(即不改变顺序),或者跳过排序。具体修改:在比较器的开头,先判断`currentSortColumn`是否为负数或者超出数据列的范围,如果是,则返回0(相等)。同时,我们注意到初始排序调用`sortTableData(-1,true)`时,我们实际上希望保持原始顺序,所以不需要进行真正的排序(即使用原始顺序)。因此,在比较器中,如果`currentSortColumn`不在有效范围内,我们就返回0。修改比较器如下:原比较器:```javaComparator<Object[]>comparator=(row1,row2)->{Objectvalue1=row1[currentSortColumn];Objectvalue2=row2[currentSortColumn];//...后续比较};```修改后:```javaComparator<Object[]>comparator=(row1,row2)->{//检查列索引是否有效if(currentSortColumn<0||currentSortColumn>=row1.length){return0;//不改变顺序}Objectvalue1=row1[currentSortColumn];Objectvalue2=row2[currentSortColumn];//...后续比较(保持不变)};```另外,我们还可以在`sortTableData`方法的开头增加一个判断:如果`columnIndex`无效(比如负数或者超出列数范围),那么就保持原来的顺序,即不进行排序(或者用默认顺序)。但为了保持代码的健壮性,在比较器内部做判断更好。注意:我们传入的`row1`和`row2`都是`Object[]`数组,其长度就是表头的列数(7列)。所以,有效列索引是0到6。因此,我们将比较器修改为:```javaComparator<Object[]>comparator=(row1,row2)->{//如果当前排序列无效,则不改变顺序if(currentSortColumn<0||currentSortColumn>=row1.length){return0;}Objectvalue1=row1[currentSortColumn];Objectvalue2=row2[currentSortColumn];if(value1==null&&value2==null)return0;if(value1==null)return-1;if(value2==null)return1;//根据不同列数据类型定制比较规则try{//数值列:2(数量),5(板材/组件),6(订购数量)if(currentSortColumn==2||currentSortColumn==5||currentSortColumn==6){doubled1=Double.parseDouble(value1.toString());doubled2=Double.parseDouble(value2.toString());returnsortAscending?Double.compare(d1,d2):Double.compare(d2,d1);}//其他列按字符串排序else{Strings1=value1.toString().toLowerCase();Strings2=value2.toString().toLowerCase();returnsortAscending?s1.compareTo(s2):s2.compareTo(s1);}}catch(NumberFormatExceptione){//解析失败时按字符串比较Strings1=value1.toString().toLowerCase();Strings2=value2.toString().toLowerCase();returnsortAscending?s1.compareTo(s2):s2.compareTo(s1);}};```这样,当我们用-1作为列索引调用排序时,比较器会直接返回0,从而保持原始顺序。另外,在`sortTableData`方法中,我们调用了`Collections.sort(allTableRowsData,comparator);`,即使比较器返回0,排序也会保持原顺序(稳定排序),但实际上我们使用的是不稳定的排序,不过因为比较为0,顺序应该保持不变。但是,注意`Collections.sort`是不稳定的排序,如果希望保持原来相等的元素的顺序,可能需要使用稳定的排序,但这里我们要求全部保持原始顺序,所以没有问题。修改后,重新运行测试。另外,我们注意到在`fillTableData`方法中,我们调用了`sortTableData(-1,true);`,然后会触发一次排序和刷新表格,这次就不会越界了。但是,我们还需要注意,在表头点击事件中,传入的`columnIndex`一定是0到6(因为表头有7列),所以不会出现负数或超出范围的情况。因此,我们只需要修改比较器内部对无效列的处理即可。请按照上述修改代码,然后重新运行。注意:由于错误日志中显示的是`index=-1`,所以我们主要处理了-1的情况,同时也处理了超出列数的情况(虽然不太可能发生)。另外,我们在创建行数据时,每行数据都是7个元素(因为`createRowData`返回长度为7的数组),所以列索引应该不会超过7,但是为了安全,我们使用`row1.length`(即7)作为判断。修改后,再次运行,看是否解决该异常。如果还有其他问题,请进一步分析。此外,代码中可能还有其他潜在问题,例如在`fillTableData`方法中,添加数据的逻辑可能存在重复添加或逻辑错误,这可能会导致行数异常多。但这不是当前崩溃的问题,所以暂不处理。下面给出修改后的`sortTableData`方法(仅修改了比较器部分):</think> 您的问题是关于 `ArrayIndexOutOfBoundsException: length=7; index=-1` 的异常。这个错误发生在 Android 应用中的 `OrderDisplayFragment.sortTableData()` 方法中。错误的原因是尝试访问数组中不存在的索引 -1(数组长度只有 7)。 ### 错误分析 1. **问题根源**: `currentSortColumn` 变量的值在某些情况下变成了 -1 2. **错误位置**: `sortTableData()` 方法中的比较器 lambda 表达式: ```java Comparator<Object[]> comparator = (row1, row2) -> { Object value1 = row1[currentSortColumn]; // 当 currentSortColumn=-1 时出错 ``` 3. **调用路径**: - `onCreateView()` 调用 `fillTableData()` - `fillTableData()` 调用 `sortTableData(-1, true)` - 初始排序传入列索引 -1 ### 解决方案 需要修改 `sortTableData()` 方法中的比较器,添加对 `currentSortColumn` 为负值情况的保护: ```java private void sortTableData(int columnIndex, boolean ascending) { // 更新排序状态逻辑保持不变... Comparator<Object[]> comparator = (row1, row2) -> { // 添加对无效列索引的保护 if (currentSortColumn < 0) { return 0; // 返回0表示相等,保持原顺序 } Object value1 = row1[currentSortColumn]; Object value2 = row2[currentSortColumn]; // 原始比较逻辑保持不变... }; // 排序和刷新逻辑保持不变... } ``` ### 修改后的完整代码 ```java 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) -> { // 保护:避免索引-1导致的数组越界 if (currentSortColumn < 0) { return 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 { 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); } }; Collections.sort(allTableRowsData, comparator); refreshTable(); } ``` ### 修复说明 1. **保护机制**: ```java if (currentSortColumn < 0) { return 0; // 返回0表示相等,保持原顺序 } ``` 当 `currentSortColumn` 为负值时(如初始排序传入的 -1),比较器会直接返回 0,不进行实际排序 2. **安全行为**: - 保持数据的原始顺序 - 避免数组越界访问 - 允许后续的列点击排序正常工作 ### 建议改进 1. **添加表头排序指示器**:在 `showSortIndicator()` 方法中实现 2. **优化初始排序逻辑**:默认使用有效列索引(如 0) 3. **增强错误处理**: ```java // 在比较器中添加额外的保护 if (currentSortColumn >= row1.length || currentSortColumn >= row2.length) { return 0; } ``` ### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值