开发笔记-UI

开发笔记主要是记录开发中遇到的一些问题和解决方案,以及引发的一些思考,不会太深入地记录问题,但是会尽可能广泛地记录涉及到的内容,方便之后整理归纳和查阅

No.1 RecyclerView 隐藏 item(多布局)的所在区域显示空白

在 RecyclerView 是多种布局的情况下,需要把 RecyclerView 的其中一个条目 GONE 掉,若只是把内容隐藏,这样就会出现一片空白区域,高度不会改变,解决方法是,隐藏时把 item 的高度宽度设置为 0,需要显示的时候再设置回来

1
2
3
4
5
6
7
8
9
10
11
12
13
if (type == TYPE1) { // 某个类型,反正就是要隐藏的类型或者状态
val param = holder.itemView.layoutParams
param.height = 0
param.width = 0
holder.itemView.layoutParams = param
...
} else { // 其他类型,要显示的类型或者状态,恢复宽高
val param = holder.itemView.layoutParams
param.height = ScreenUtil.dp2px(context, 46F)
param.width = RelativeLayout.LayoutParams.MATCH_PARENT
holder.itemView.layoutParams = param
...
}

No.2 ListView getChildAt() 取得位置错误问题

有时候我们需要获取 ListView 或 RecycleView 的某个 item 的 view 对象来做一些处理,发现使用 getChildAt(position: Int) 这个方法取到的不是想要的 item,而是可视的第 position 位置的 item,也就是说 position 只是从第一个可以看到的 item 算起的,这样就和实际列表中的第 position 个是不一样的,这样就需要使用如下代码来取得实际的位置

1
2
3
val itemPosition = 1 // 想要修改的 item position
val targetPosition = itemPosition - listView.getFirstVisiblePosition() // 实际的目标 item position
val itemView = listView.getChildAt(targetPosition)

这里需要注意的是 itemView 在可视范围上方时,会返回 null,在源码中可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Returns the view at the specified position in the group.
*
* @param index the position at which to get the view from
* @return the view at the specified position or null if the position
* does not exist within the group
*/
public View getChildAt(int index) {
if (index < 0 || index >= mChildrenCount) {
return null;
}
return mChildren[index];
}

所以需要对空值做一下处理

No.3 performClick 和 callOnclick 的区别

View 类的 performClick 和 callOnclick 函数都可以实现点击,不用用户手动点击,直接触发 View 的点击事件。区别有如下两点:

  1. API 等级

    performClick 是在 API 1 中加入,callOnClick 是在 API 15 中加入

  2. 代码实现层面

    看两个方面的代码实现,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    /**
    * Directly call any attached OnClickListener. Unlike {@link #performClick()},
    * this only calls the listener, and does not do any associated clicking
    * actions like reporting an accessibility event.
    *
    * @return True there was an assigned OnClickListener that was called, false
    * otherwise is returned.
    */
    public boolean callOnClick() {
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
    li.mOnClickListener.onClick(this);
    return true;
    }
    return false;
    }

    /**
    * Call this view's OnClickListener, if it is defined. Performs all normal
    * actions associated with clicking: reporting accessibility event, playing
    * a sound, etc.
    *
    * @return True there was an assigned OnClickListener that was called, false
    * otherwise is returned.
    */
    public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
    playSoundEffect(SoundEffectConstants.CLICK);
    li.mOnClickListener.onClick(this);
    result = true;
    } else {
    result = false;
    }
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
    }

    从代码中可以看出,callOnClick 是 performClick 的简化版,不包含点击播放声音,不具有辅助功能,辅助功能官方介绍如下:

    许多 Android 用户有不同的能力(限制),这要求他们以不同的方式使用他们的 Android 设备。这些限制包括视力,肢体或与年龄有关,这些限制阻碍了他们看到或充分使用触摸屏,而用户的听力丧失,让他们可能无法感知声音信息和警报,Android 提供了辅助功能的特性和服务帮助这些用户更容易的使用他们的设备,这些功能包括语音合成、触觉反馈、手势导航、轨迹球和方向键导航。Android 应用程序开发人员可以利用这些服务,使他们的应用程序更贴近用户

No.4 android ems 具体意义

在 android 里 setEms() 作用是设置 TextView 的字符宽度,em 是一个印刷排版的单位,表示字宽的单位,em 字面意思为:equal M(和M字符一致的宽度为一个单位)简称 em,而 ems 是 em 的复数表达,所以 ems 和字节什么的都是没关系的,只是和字宽度有关系,而且由于各个手机版本自定义字体等问题,所以设置 ems 意义不大,只能作为参考,当设置该属性后,控件显示的长度就为10个字符的长度,超出的部分将不显示,而且 EditText 的属性,只有在
android:layout_width="wrap_content" 时,才会显示,如果是 android:layout_width="match_parent"
时,则不会有变化

No.5 图片显示上下有空白的解决办法

在使用 ImageView 时,采用不同的 scaleType 属性可以调整图片的缩放类型,但是有时会导致图片上下方出现空白,这时就要设置 android:adjustViewBounds="true"

关于 ImageView 的 adjustViewBounds 属性

取值为 true 时:

Adjust the ImageView’s bounds to preserve the aspect ration of its drawable.

调整 ImageView 的界限来保持图像纵横比不变。
这并不意味着 ImageView 的纵横比就一定和图像的纵横比相同,XML定义里的 android:adjustViewBounds="true" 会将这个 ImageView 的 scaleType 设为 fitCenter,不过这个 fitCenter 会被后面定义的 scaleType 属性覆盖(如果定义了的话),除非在 Java 代码里再次显式调用 setAdjustViewBounds(true)

1. 如果设置的 layout_width 与 layout_height 都是定值

那么设置 adjustViewBounds 是没有效果的,ImageView 将始终是设定的定值的宽高

2. 如果设置的 layout_width 与 layout_height 都是 wrap_content

那么设置 adjustViewBounds 是没有意义的,因为 ImageView 将始终与图片拥有相同的宽高比(但是并不是相同的宽高值,通常都会放大一些)

3. 如果两者中一个是定值,一个是 wrap_content

比如 android:layout_width="100px"android:layout_height="wrap_content" 时,ImageView 的宽将始终是 100px,而高则分两种情况:

  • 当图片的宽小于 100px 时,layout_height 将与图片的高相同,即图片不会缩放,完整显示在 ImageView 中,ImageView 高度与图片实际高度相同,图片没有占满 ImageView,ImageView 中有空白
  • 当图片的宽大于等于 100px 时,此时 ImageView 将与图片拥有相同的宽高比,因此 ImageView 的 layout_height 值为:100 除以图片的宽高比,比如图片是 500X500 的,那么 layout_height 是 100,图片将保持宽高比缩放,完整显示在 ImageView 中,并且完全占满 ImageView

No.6 硬件加速导致画线不显示(有待深入研究,先总结现象)

  • Android 9.0 drawLine drawPath 都可以正常实现画线,抗锯齿,实现虚线
  • Android 9.0 以下
    • drawLine 一定能画出线,但是不能画出虚线 (抗锯齿有效)
    • drawPath 一定能画出虚线,但是大斜率的时候线会消失 (抗锯齿无效) 因为x y值过大,不进行绘制了

No.7 在约束布局中使用 include 标签报错

在约束布局 ConstraintLayout 中引入了一个布局,然后给引入布局添加了底部约束,让它距离底部 8dp,但是引入布局仍然出现在顶部,并报错如下:

1
Layout parameter layout_marginBottom ignored unless both layout_width and layout_height are also specified on <include> tag

在约束布局中引入新的控件或者布局时,若不重新指定一下控件或者布局的宽高,那么给它添加的约束便会失效,给 include 标签中添加上 layout_width 和 layout_height 属性即可

No.8 BottomSheetDialog 输入框输入文字时上下跳动

开发中遇到一个问题,一个 BottomSheetDialog 里面有上下两个输入框,上方的输入框在软键盘弹出时,并没有被软键盘顶着,所以输入时,BottomSheetDialog 表现正常,但是下方的输入框,是被软键盘顶的,在输入时,BottomSheetDialog 中的 TextView 要根据输入的内容作出变化,这时,一旦 TextView 的属性为 wrap_parent ,他的的文字发生变化时,高度宽度随之发生变化,BottomSheetDialog 就会上下跃动,体验比较差,后来发现只要给定 TextView 的高度和宽度,就可以避免这个问题,比较低版本的 Android 系统中似乎不会出现,后续有待排查

No.9 关于 ViewPager 嵌套 RecyclerView,当 RecyclerView 滑动到尽头后,不希望 ViewPager 被连带拖动的情况

有下面两种情况

  1. 当 RecyclerView 外部是一个自定义的 View,你可以在这个自定义的 View 中加入下面的代码,这样,外部可以判断如果 rv 不可滑动,就做一个类似拦截的操作,避免引起滑动(还需要研究 dispatchTouchEvent 相关内容,弄明白这样为什么有效)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    private int lastX = 0;
    private int lastY = 0;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
    boolean intercept = super.dispatchTouchEvent(ev);
    switch (ev.getAction())
    {
    case MotionEvent.ACTION_DOWN:
    break;
    case MotionEvent.ACTION_MOVE:
    intercept = needIntercept(ev);
    break;
    default:
    }
    lastX = (int)ev.getX();
    lastY = (int)ev.getY();

    return intercept;
    }

    private boolean needIntercept(MotionEvent ev)
    {
    // 水平滚动距离大于垂直滚动距离则拦截
    float deltaX = ev.getX() - lastX;
    float deltaY = ev.getY() - lastY;
    boolean isHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
    if (isHorizontal)
    {
    if (deltaX > 0)
    {
    // 往右滑动
    return !recyclerView.canScrollHorizontally(-1);
    }
    else
    {
    // 往左滑动
    return !recyclerView.canScrollHorizontally(1);
    }
    }
    else
    {
    return true;
    }
    }
  2. 自定义一个 View 继承 RecyclerView,写如下代码,有点像内部拦截法,需要弄懂 requestDisallowInterceptTouchEvent 相关内容
    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    /*---解决垂直 ViewPager 嵌套水平 RecyclerView 横向滑动到底后不滑动 ViewPager start ---*/
    ViewParent parent = this;
    while(!((parent = parent.getParent()) instanceof ViewPager)); // 循环查找 ViewPager
    parent.requestDisallowInterceptTouchEvent(true);
    return super.dispatchTouchEvent(ev);
    }

No.10 View 的一些踩坑

1. GestureDetector 无反应

使用 GestureDetector 时,无论什么手势,均无反应,解决方法:需要重写 onDown 方法,可看参考文章

2. 界面闪烁

View 的逻辑不能太重,运算不能太多,重复调用的的 path 相关操作要记得 reset,可看参考文章


开发笔记-UI
https://enderhoshi.github.io/2024/03/05/开发笔记-UI/
作者
HoshIlIlI
发布于
2024年3月5日
许可协议