一日一钱,千日一千。绳锯木断,水滴石穿。

bug系列—1像素引发的血案

Posted on By TinyVampirePudge

bug系列—1像素引发的血案

背景描述

使用RecyclerView实现了一个类似WheelView的效果,这里RecyclerView的高度是5个item的高度。大致效果如下图所示:

具体项目地址:

WheelViewByRecyclerView

这里选用RecyclerView自己实现这个效果的原因是,UI给的设计图中,要求文案支持两行,如果超过两行了,需要支持文字自动缩小。而现有的三方库不支持换行这个操作,即使在三方库的基础上进行更改也不容易,所以这里决定自己实现。

为了实现RecyclerView滑动停止后的弹性滑动,这里使用了LinearSnapHelper,这样在滑动停止后,一定会有item停留在中间的选中位置。

接着就是获取停止后的位置,这里通过linearLayoutManager.findFirstVisibleItemPosition()方法获取到第一个可见item的位置pos,根据这pos就可以获取到中间item的角标。

RecyclerView的布局文件如下:

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="225dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@id/view_line" />

RecyclerView的adapter布局如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:layout_marginLeft="@dimen/dimen_16dp"
        android:layout_marginRight="@dimen/dimen_16dp"
        android:gravity="center"
        android:textColor="@color/c_0B1D32"
        android:textSize="@dimen/dimen_18dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="啊哈哈哈" />

</android.support.constraint.ConstraintLayout>

我们给RecyclerView固定了高度225dp,刚好是5个item的高度(45dp * 5),理论上我们的逻辑没毛病

到此我们的核心逻辑完成,大部分机型上都正常工作。

1、问题描述

诺基亚  X71安卓系统 项目选择出现错位,最后一个项目选择不到
上述问题 更换 华为DLI-AL10 也是一样

错位的效果是选择的都是上一个的数据,这也就表示最后一个元素无法选中。

初步猜想是机型分辨率适配问题,导致上方多显示了一个item,进而导致获取到的第一个可见item的位置错误。

2、问题定位

我们先看下正常显示的手机:

RecyclerView的实际高度:625px

单个item的实际高度:125px

经过计算:625 = 125 * 5,所以显示正常。

接下来我们看下出现异常的手机:

如下图所示,RecyclerView的实际高度为391px:

单个item的实际高度为78px:

我们发现低版本手机上,RecyclerView的实际高度比5个item高度总和多了一像素(391 - (78 * 5) = 1 ),导致屏幕上实际显示了6个item。

所以最终问题出现的原因确定了,是dp转换成px后的误差问题。

具体原因是 ScreenUtil.dip2px(mContext, 45) * 5;的值跟ScreenUtil.dip2px(mContext, 225)的值不相等,他们的值在这里相差1px,但就是这一像素导致了我这里的错位bug。

3、方案确定

既然是误差问题,那就有可能多,也有可能少,所以我们使用linearLayoutManager.findFirstCompletelyVisibleItemPosition();代替linearLayoutManager.findFirstVisibleItemPosition();的方案也依然会遇到错位的问题。

最终我决定在代码中动态设置一次RecyclerView的高度,以此来修复误差问题。

// 重新设置RecyclerView高度,避免误差
ConstraintLayout.LayoutParams lp = (ConstraintLayout.LayoutParams) recyclerView.getLayoutParams();
lp.height = ScreenUtil.dip2px(mContext, 45) * 5;
recyclerView.setLayoutParams(lp);

至此问题解决,如果觉得还可以的话记得点个赞哦!