三. View的经常使用API

不忘初心 砥砺前行, Tomorrow Is Another Day !html

相关文章

本文概要:git

  1. 触摸事件相关
  2. View工做流程相关
  3. 其余工具
  4. 常见问题

一. 触摸事件相关

1.1 ScrollTo和ScrollBy

适合对View内容的滑动,只对View的scrollX、scrollY有影响,对View的大小和位置没有影响.github

对应源码canvas

//增量滚动,增量是指在已有的滚动偏移量的增量
public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
}

//绝对滚动
public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
}

复制代码

源码中两个比较重要的参数:bash

  • mScrollX : 水平方向滚动的偏移量(相似绝对坐标,能够理解为绝对偏移量)
    • 计算方式: mScrollX = 0(初始位置) - 100(结束位置) = -100
  • mScrollY : 竖直方向滚动的偏移量

题外话:网上部分博客说成是坐标,看了mScrollX注释"The offset, in pixels, by which the content of this view is scrolled horizontally." 用个人渣英语一看,特么不说的是"View的内容在水平方向滚动的偏移量"吗,因此我的以为说偏移量更加严谨.若有不对欢迎拍砖,请指点.app

由于内部调用invalidate致使重绘,不会走测量布局过程,因此才有上述结论.因为是对View的内容进行滑动,因此需注意滑动方向的问题.ide

对应源码工具

//由父元素调用dispatchDraw,而后调用三个参数的draw方法.
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

        //...省略部分代码
        int sx = 0;
        int sy = 0;
        if (!drawingWithRenderNode) {
            computeScroll();
            sx = mScrollX;
            sy = mScrollY;
        }

        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
        final boolean offsetForScroll = cache == null && !drawingWithRenderNode;

        int restoreTo = -1;
        if (!drawingWithRenderNode || transformToApply != null) {
            restoreTo = canvas.save();
        }
        if (offsetForScroll) {
            //平移的是画布,这就解释了为何传入的方向值要相反.
            canvas.translate(mLeft - sx, mTop - sy);
        } 
            //...省略部分代码       
}        
复制代码
1.2 layout 、offsetLeftAndRight与offsetTopAndBottom

对View的L、T、R、B属性有影响.布局

示例代码post

private void scrollMethodOnLayout(int offsetX, int offsetY) {
        //layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
        offsetLeftAndRight(offsetX);
        offsetTopAndBottom(offsetY);
    }

复制代码
1.3 LayoutParams

对布局参数有影响

示例代码

private void scrollMethodOnLP(int offsetX, int offsetY) {
        //在不清楚父View是什么类型时,可使用ViewGroup.MarginLayoutParams
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
        layoutParams.leftMargin += offsetX;
        layoutParams.topMargin += offsetY;
        setLayoutParams(layoutParams);
    }
复制代码
1.4 动画
  • 视图动画适合没有交互性的View
  • 属性动画适合有交互性的View,只对View具体属性值有影响

下一篇详细讲解动画相关.

1.5 Scroller

实现弹性滑动

使用步骤:

  1. 初始化Scroller
  2. startScroll开启滑动过程
  3. 在View中重写computeScroll方法

示例代码

//1. 第一步
mScroller = new Scroller(context);

//2. 第二步
private void scrollMethodOnScroller(int offsetX, int offsetY) {
        mScroller.startScroll(((View) getParent()).getScrollX(), ((View) getParent()).getScrollY(),
                offsetX, offsetY, 3000);
        invalidate();
}

//3. 第三步
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

复制代码

原理:

  1. 首先经过startScroll保存相关的滑动坐标信息.
  2. 当咱们调用invalidate进行重绘时,系统会回调computeScroll方法.
  3. 在computeScrollOffset方法里根据时间的流逝,去计算是否滑动完成.
    • 未完成,则继续invalidate进行重绘.

具体流程能够看源码,比较简单这里就不单独分析了.

根据上面Scroller的原理能够总结出一个弹性滑动的核心思想,那就是在一个时间段里,将一次大的滑动分解成屡次小的滑动,除了系统的提供的Scrooler,还可使用动画与延迟策略去实现.

1.6 ViewConfiguration

View系统配置信息

参考:blog.csdn.net/lfdfhl/arti…

1.7 VelocityTracker

速度追踪,通常用于识别快速滑动

使用步骤:

  1. 初始化VelocityTracker
  2. 接管触摸事件,获取当前滑动速度
  3. 重置并回收.

参考:blog.csdn.net/lfdfhl/arti…

1.8 GestureDetector

手势识别

使用步骤:

  1. 初始化GestureDetector
  2. 接管触摸事件
  3. 处理手势识别回调

参考: blog.csdn.net/lfdfhl/arti…

1.9 ViewDragHelper

通常用于自定义ViewGroup中处理子View的拖动.

使用步骤:

  1. 初始化ViewDragHelper
  2. 接管触摸事件
  3. 处理回调

参考:

1.10 setTranslationX和setTranslationY

只是将View位置改变,不会触发View的重绘,这是与前面ScrollTo的最大区别.

  • setTranslationX : 设置View在水平方向,相对于它的left位置偏移量.
  • setTranslationY : 同理
1.11 requestDisallowInterceptTouchEvent

请求父元素不要拦截个人事件

二. View工做流程相关

2.1 inflate(布局解析器)

对应源码

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        //调用了三个参数的方法
        return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
}
复制代码
  1. 当root为空时,那么会解析xml布局,最后返回xml中的根节点.
  2. 当root不为空时,那么会解析xml布局而且添加到根节点root下,最后返回这个根节点root.

另外有一种特殊状况直接采用三个参数的方法时, 3. 当root不为空,attachToRoot为false时,这时不会将解析的xml布局添加到根节点root中,最后返回xml中的根节点.

这时root的做用,仅仅只是为了给xml布局中的根节点提供layoutParams的参数属性,不然layoutParams的参数属性会失效,由于其xml中的根节点压根不知道本身父容器是谁.具体用例好比在已经退出历史舞台的ListView中getView时,inflate防止item的布局参数失效.

若是想详细的了解inflate的源码实现细节,能够参考郭婶的博客,地址blog.csdn.net/guolin_blog…

2.2 onFinishInflate

当xml布局文件被解析完成时.

2.3 ViewTreeObserver

View树观察者,包括布局、绘制、触摸事件变化等.

示例代码

  • 获取view的宽高
ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
复制代码
2.4 requestLayout

触发测量和布局过程,不会触发重绘.

使用场景:

通常用于View的位置和大小改变时.

2.5 onSizeChange

View的大小发生改变时.

在View进行布局过程会被调用.在layout-setFrame-onSizeChange

使用场景:

通常用于初始化与View的大小相关成员变量.

2.6 setWillNotDraw

是否不绘制,默认是true

因为ViewGroup默认是不绘制本身的,除非设置了背景或者调用了setWillNotDraw设置为false.才会绘制本身

使用场景:

通常用于自定义ViewGroup而且想要实现它自己的绘制时,就能够设置一个背景或者直接调用setWillNotDraw(false)

2.7 invalidate与postInvalidate

触发View的重绘,但不会调用测量和布局过程.

二者区别:

  • invalidate : 只能用于主线程
  • postInvalidate : 能够用于子线程更新UI
    • 内部原理最终仍是经过handler发送message到主线程,而后调用invalidate.
2.8 onAttachFromWindow与onDetachFromWindow
  • onAttachFromWindow : 当一个View绑定到Window上时的调用.
    • 通常用于初始化一些任务等等
  • onDetachFromWindow :同理.

三.其余工具

3.1 TypeValue

将普通数值转换成对应数据类型的数值

示例代码

TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,666,getResources().getDisplayMetrics());
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,666,getResources().getDisplayMetrics());
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,666,getResources().getDisplayMetrics());
复制代码
3.2 DisplayMetrics

除了系统提供的TypeValue,还能够本身实现转换.

示例代码

public static int dp2Px(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }


    public static int px2Dp(Context context, float px) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (px / scale + 0.5f);
    }

    public static int px2Sp(float pxValue, Context context) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }


    public static int sp2Px(float spValue, Context context) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (spValue * scale + 0.5f);
    }
    
复制代码

四.常见问题

在前面几篇文章中有几个问题一直未进行解释,下面对此依次说明.

  • 为何须要针对wrap_content时进行处理,否则效果等同match_parent?

解答: 默认返回的是specSize(parentSize父元素可用空间)

解决方式: 示例代码

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        
        //处理wrap_content,设置一个默认的宽或高
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, 200);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSize, 200);
        }

    }
复制代码
  • getMeasureWidth和getWidth的区别?

解答: 产生时机不一样,前者产生于measure过程,后者产生于layout过程

  • onCreate/onStart/onResume中没法获取宽高?

解答: 经过了解ActivityThread过程能够得知,activity生命周期方法和view的measure过程不是同步的.

解决方式: 示例代码

  1. Activity/View#onWindowFocusChanged
@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
            Log.d(TAG, "onWindowFocusChanged, width= " + view.getMeasuredWidth() + " height= " + view.getMeasuredHeight());
        }
    }
复制代码
  1. view.post(runnable)与ViewTreeObserver
@Override
    protected void onStart() {
        super.onStart();
        //*********
        view.post(new Runnable() {

            @Override
            public void run() {
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
        //ViewTreeObserver已经说明,见本文2.3.
    }
复制代码
  1. 手动view.measure(int widthMeasureSpec, int heightMeasureSpec) 须要根据view的layoutParams区分
//match_parent,没法得知measureSpec的parentSize


private void measureView() {
        //wrap_content
        int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);
        //具体数值
        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
        view.measure(widthMeasureSpec, heightMeasureSpec);
        Log.d(TAG, "measureView, width= " + view.getMeasuredWidth() + " height= " + view.getMeasuredHeight());
    }

复制代码

关于经常使用工具暂时先到这了,将来也会持续更新,用做实际开发中API文档使用.

因为本人技术有限,若有错误的地方,麻烦你们给我提出来,本人不胜感激,你们一块儿学习进步.

参考连接:

相关文章
相关标签/搜索