自定义View笔记

自定义View

view的事件体系
  1. 基础
    1. view的位置由top, left, right, bottom决定,这些坐标都是相对view的父容器来说的,
      • 在View平移的过程中,top, left代表的是原始左上角的位置信息,其值并不会发生改变,此时发生改变的是x, y, translationX, translationY
    2. MotionEvent和TouchSlop
      • 通过MotionEvent对象我们可以获得点击事件发生的x和y坐标,getX/getY, 和getRawX/getRawY,其中getX/getY返回的是相对于当前view左上角的x和y坐标,而getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标
    3. VelocityTracker,GestureDetector和Scroller
      • 使用GestureDetector的情景:如果只是监听滑动相关,自己在onTouchEvent中实现即可,如果要监听双击这种行为,才使用GestureDetector
  2. view的滑动
    1. 使用scrollTo/scrollBy
      • scrollTo实现了当前位置的绝对滑动,scrollBy实现了当前位置的相对滑动
      • scrollTo scrollBy 只能改变View内容的位置而不能改变View在布局中的位置
      • 关于mScrollXmScrollY, 位置改变需注意
    2. 通过动画给View施加平移效果
      • 主要操作View的translationXtranslationY属性
      • View动画是对View的影像做操作,并不能真正改变View的位置参数,包括宽高,如果希望动画后状态得以保留还必须将fillAfter设置为true
    3. 使用属性动画
    4. 荣国改变View的LayoutParams来使得View重新布局
  3. View的弹性滑动
    1. 使用scrollTo
      1. 使用Scroller
      2. 通过动画onAnimationUpdate方法:
        1. 模仿Scroller,通过改变百分比配合scrollTo方法来完成View的滑动,注意只能是View的内容而非View本身
    2. 使用延时策略
      1. 使用Handler#postDelayed
      2. 使用Thread#sleep
View的事件分发机制
  1. 点击事件的传递规则:

    • public boolean dispatchTouchEvent(MotionEvent ev) {
          boolean consume = false;
          if (onInterceptTouchEvent(ev)) {
              consume = onToucnEvent(ev);
          } else {
              consume = child.dispatchTouchEvent(ev);
          }
          return consume;
      }
      复制代码
    • 当一个view需要处理事件时:

      • 优先级:OnTouchListener>onTouchEvent>OnclickListener
      • 传递顺序:Activity->Window->View
      • 如果底层View的onTouchEvent返回false,则父容器的onTouchEvent将会被调用,如果所有元素都不处理这个事件,那么Activity的onTouchEvent将会被调用
View的滑动冲突
view的工作流程
  1. ViewRoot和DecorView的概念

    1. View的三大流程均是通过ViewRoot来完成的。View的绘制流程从ViewRoot的performTraversals开始,依次调用performMeasure, performLayout, performDraw三个方法
  2. measure过程

    1. MeasureSpec

      1. MeasureSpc代表32位int值,高2位代表SpecMode,低30位代表SpecSize
      2. SpecMode类别:EXACTLY对应match_parent,AT_MOST对应wrap_content
      3. 对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定
    2. onMeasure

      1. setMeasuredDimension会设置view的宽高

      2. 直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent

        //只需要给View指定一个默认的内部宽高(mWidth, mHeight)
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
            if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(mWidth, mHeight);
            } else if (widthSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(mWidth, heightSpecSize);
            } else if (heightSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(widthSpecSize, mHeight);
            }
        }
        复制代码
      3. measure完成后,通过getMeasuredWidth/Height方法就可以获得View的测量宽高,比较好的习惯是在onLayout方法中获取View的测量宽高或最终宽高

      4. 在activity中获得View的宽高

        • Activity/View#onWindowFocusChanged
        • view.post(runnable)
        • ViewTreeObserver
        • view.measure
  3. layout过程

  4. draw过程

    1. 步骤
      • 绘制背景background.draw(canvas)
      • 绘制自己onDraw
      • 绘制children(dispatchDraw)
      • 绘制装饰onDrawScrollBars
    2. 如果我们自定义控件继承ViewGroup并且本身并需要通过onDraw来绘制内容时,我们需要显式地关闭WILL_NOT_DRAW这个标记位
自定义View
  1. 分类:

    • 继承View重写onDraw方法,主要用于实现一些不规则效果
    • 继承ViewGroup派生特殊的Layout,主要用于实现自定义布局
    • 继承特定的View(比如TextView)
    • 继承特定的ViewGroup(比如LinearLayout)
  2. 需要实现:

    • 支持wrap_content

    • 支持padding:只要在onDraw方法中考虑一下padding即可,

    • 提供自定义属性:

      • 在values目录下创建自定义属性的XML,比如attrs.xml,

        <?xml version="1.0" encoding="utf-8"?>
        <resources>
        	<declare-styleable name="CircleView">
            	<attr name="circle_color" format="color" />
            </declare-styleable>
        </resources>
        复制代码

        格式有reference(是指资源id), dimension(指尺寸),string,integer,boolean

      • 在View的构造方法中解析自定义属性的值并做相应的处理

        public CirecleView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
            mColor = a.getColor(styleable.CircleView_circle_color, Color.RED);
            a.recycle();
            init();
        }
        复制代码
      • 在布局文件中使用自定义属性

        注意:必须在布局文件中添加schemas声明:

        xmlns:app=http://schemas.android.com/apk/res-auto
        复制代码
    • 不要在View中使用Handler

    • View中如果有线程或者动画,需要在onDetachedFromWindow方法中及时停止,防止内存泄漏

    参考:《Android开发艺术探索》

转载于:https://juejin.im/post/5d0851bf6fb9a07ec07fc131

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值