那些好玩的 android 小事

那些好玩的 android 小事

本文记录的是一些在开发时遇到的好玩的东西,一些容易出错的地方,一些迷惑的地方, 虽然记录的东西很简单,但是又特别的细节。

  1. View 的 setOnclickListener(...) 与 setClickable

    view.setClickable(false);
    view.setOnclickListener(...);

    则 该 view 仍然为可点击状态,因为
    setOnClickListener()里面会把 view 设置为 clickable , 可点击状态:

    // view 的 源码  
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
    

    会首先检查该 view 是否可点击,如果不可点击,则会把它设置为可点击状态。

  2. ImageView.setAlpha(int) 与 ImageView.setAlpha(float) 的区别

    这是一个文档里,很容易踩进去的坑。

    setAlpha(int) 是对 image 进行 alpha 进行变化, 范围是 0 ~ 255;

    setAlpha(float) 是对 view 进行 alpha 进行变化,范围是 0f ~ 1f;

    两个方法 做 透明度变化的对象不同,要千万注意,注意!!!, 如果混用,就会出现问题的。

    例如,如果是想利用 float 进行设置,刚开始设置了 setAlpha(0), 后面都是 setAlpha(float), 则会出现 这个 view 永远展示不出来的情况, 刚开始设置 setAlpha(0f) 则是正确的。

    注意:官方已经抛弃了 setAlpha(float) 这个方法,推荐使用 setImageAlpha(int) (API 16 以上才会生效) 这个方法.

  3. 在 自定义 view 中, paint 的 setColor() 与 setAlpha() 的关系

    paint.setColor(#12ffffff);
    如果设置的颜色里面包含了 透明度, 则 该画笔 的透明度 不一定就是 12;

    /**
    * ... ts alpha can be any value, regardless of the values of r,g,b
    */
    public void setColor(@ColorInt int color) {
        nSetColor(mNativePaint, color);
    }
    

    它的 alpha 可能会变化,因为受 setAlpha 的影响

     /**
     * Helper to setColor(), that only assigns the color's alpha value,
     * leaving its r,g,b values unchanged. Results are undefined if the alpha
     * value is outside of the range [0..255]
     *
     * @param a set the alpha component [0..255] of the paint's color.
     */
    
    public void setAlpha(int a) {
        nSetAlpha(mNativePaint, a);
    }
    
    

    setAlpha() 会覆盖 setColor 中的 透明度, 所以 当你做自定义 view 的变换时,如果同时设置了 setColor 和 setAlpha 则 透明度会有后者决定。

  1. 图片的时间戳问题

    在 媒体库里面 MediaStore.Images 里面, ImageColumns.DATE_TAKEN, 这项属性,描述的是该图片的时间戳。但是当你修改该图片后,该时间 DATE_TAKEN, 会怎么变化呢?假设原图片(A)的时间为 2018.01.02 13:00, 那么修改改图片后,一般会保留原图片,生成一个新的图片(B),那么这个新的图片的时间为多少呢?首先猜一猜,要么和原图一样,要么是现在修改的时间。

    但是!!!事实上,当我去打印这个时间的时候,竟然发现,修改后的 B 的时间戳,竟然小于 原图片 A 的时间!!!,也就是说,这个时间 会早于 2018.01.02 13:00 可能是 2018.01.02 12:59.

    这个不是特别会影响功能的地方,我也是偶然发现的,在这里记录一下,也说不定是错的,我只测试了我手上的几款机型,可能根据不同的机型会有所不同吧。

  1. padding 与 margin 对同一个控件的影响

    例如:父控件为LinearLayout, 子控件为button,下面两种设置的方式效果是一样的:

     1. 当父控件设置了`padding="8dp"`
         padding 是内边框, 使得该父控件里的子view的空间都会减少8dp
     2. 子控件设置了`layout_margin="8dp"`
         margin 是相对button而言的,使得自身距离父控件各个方向有8dp的距离;
    

    上面两种,实际button的点击区域,view的绘制区域都是相同的,但是padding后使得linearlayout里content会缩小8dp的距离,而margin则不会影响content的区域大小,从而造成一些特定情况下的问题。在margin下,button可以显示设置的阴影,而padding则没有足够的content去显示阴影。

    说的有点不清楚,可能需要特定的情况下,才可以发现区别,但是, padding 会导致里面 的 子 view button 的大小不能超过 padding 的限制, 是父 view 对 子view 的限制,是被动的; 而 margin 是一个主动的行为,是子 view 对 自身的一个限制;

    下面看一个

  2. 数据库的操作条数限制 : 1000 条

    Crash : SQLiteException: Expression tree is too large (maximum depth 1000)
    

    原因: 在 sqlite 语句中 的 筛选条件里面 包含了太多的内容项,超过了1000 个,就会 crash; 这个是直接写在代码里的,是 sqlite 直接抛出的异常.

    通常会出现在 删除记录时,例如, 筛选条件 whereCause.append(ID + "=" + list.get(i).id), 然后循环添加 要删除的对象,如果 list 内容太多,就会造成 whereCause 十分的庞大,当超过 1000 深度时,便会 发生 这个 crash .

    解决方案:

    1. 手动对 whereCause.append 进行限制,如果超过 1000 , 则执行 sqlite 删除语句,然后接着 另外一个 whereCause 去 添加条件, 再次去执行 删除语句。

    2. 如果条件可以替换为 IN 处理

      whereCause = "Table.column._ID IN (ids[1], ids[2], ids[3], ...);
          
      whereCause = "Table.column._ID = ids[1] OR Table.column._ID = ids[2] OR Table.column._ID = ids[3] ...";
          
      上面两者是等价的, 但是 第一种不会造成 上述 exception, in 只代表一条语句,但是 or 却有多个 or 出现
      
  3. ConstraintLayout 中的 Group 控件要慎用!

    现在更多的都会使用 ConstraintLayout 布局,因为它太强大了,减少了很多 view 的层级。

    在约束布局中,有时会想要同时控制两个以上的 view 的可见性,一般我们都是通过 Group 这个官方提供的控件去设置。

    代码示例如下:

    
    

    当我们设置 preview_layout_group.setVisibility(...), 会同时对两个 view(preview_image_view, preview_delete_view) 同时生效,比较方便。

    但是,当我们这样设置的时候,有可能会出现里面子 view 设置setVisibility() 时失效的现象!!!

    假设,我们设置 preview_layout_group.setVisibility(VISIBLE), 然后想对里面的 deleteView 设置为不可见,那么实际上是不会生效的。 deleteView imageView 都是可见的。

    • 这里跟我们之前设置 view 的可见性不同的是,以前一般都是设置了父 view 可见后,在针对个别 view 设置它的不可见。

    • 如果我们使用了 Group 时, Group 并不是父 view。它是一个单独的控件去控制里面所有关联 view 的可见性,而且,是一种优先级比较大的方式去设置了里面子 view 的可见性。

      所以当你设置了 Group 为可见时,里面的子 view 你设置了不可见,子 view 的设置是不会生效的!!!

    所以要慎重的使用 Group,它其实很实用,会很方便控制一些 view 的共同可见性,但是,当使用的时候,一定要确保里面所有关联的子 view 之间的可见性时完全一样的,要么都可见,要么都不可见!

  1. RecyclerViewscrollbar 和分割线 Item.Decoration 的遮挡问题
    有时候,会出现 RecyclerViewscrollbar 显示不完全,或者 scrollbar 被「分割线」分开的现象,这部分涉及到的内容比较多。详细在下面这篇文章中:
    RecyclerView 的 scrollbar 和 ItemDecoration 的绘制和遮挡问题

  2. TextView 里面的 moveCursorToVisibleOffset() 方法

    该方法在源码中注释很详细:当 TextView 中的 mTextSpannable 的实例且不只一个字符时,当文本滚动时「文本太多,可滑动」,编辑的光标会随着滚动而移动。源码如下:

     /**
     * Move the cursor, if needed, so that it is at an offset that is visible
     * to the user.  This will not move the cursor if it represents more than
     * one character (a selection range).  This will only work if the
     * TextView contains spannable text; otherwise it will do nothing.
     *
     * @return True if the cursor was actually moved, false otherwise.
     */
    public boolean moveCursorToVisibleOffset() {
        if (!(mText instanceof Spannable)) {
            return false;
        }
        int start = getSelectionStart();
        int end = getSelectionEnd();
        if (start != end) {
            return false;
        }
    
        // First: make sure the line is visible on screen:
    
        int line = mLayout.getLineForOffset(start);
    
        final int top = mLayout.getLineTop(line);
        final int bottom = mLayout.getLineTop(line + 1);
        final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
        int vslack = (bottom - top) / 2;
        if (vslack > vspace / 4) {
            vslack = vspace / 4;
        }
        final int vs = mScrollY;
    
        if (top < (vs + vslack)) {
            line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
        } else if (bottom > (vspace + vs - vslack)) {
            line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
        }
    
        // Next: make sure the character is visible on screen:
    
        final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
        final int hs = mScrollX;
        final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
        final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
    
        // line might contain bidirectional text
        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
        final int highChar = leftChar > rightChar ? leftChar : rightChar;
    
        int newStart = start;
        if (newStart < lowChar) {
            newStart = lowChar;
        } else if (newStart > highChar) {
            newStart = highChar;
        }
    
        if (newStart != start) {
            Selection.setSelection(mSpannable, newStart);
            return true;
        }
    
        return false;
    }
    

    但是呢? 当我们不需要该效果,期望光标不随着文本的滚动而变化位置,那么重写该方法返回 false 即可。

    @Override
    public boolean moveCursorToVisibleOffset() {
        return false;
    }
    
  1. To be continued ...

上面是个人在开发过程中遇到的一些比较好玩的事情,有时候敲代码蛮枯燥,发现的一些小的惊奇的点,会让自己很开心,就像在沙滩上拾贝一样,偶尔发现几个特别奇特的贝壳, 会特别开心,这也算代码的魅力吧。 会持续更新, 发现一些好的点都会补充上去~

大家有什么感觉好的点,也可以指出来,如有哪里不对的地方, 水平有限,也请指出来,谢谢~~~

你可能感兴趣的:(那些好玩的 android 小事)