自定义View Demo(有小例子,适合初学者)

Demo链接: 点击打开链接

包括自定义继承view,FrameLayout,,TextView等实现进度条,弹性滑动等功能

.为什么要用自定义控件

1.特定的显示风格 (特殊效果)

2.处理特有的用户交互(例如TextView滑动文字)

3.优化我们的布局(一次测量绘制的过程慢)

4.封装等....(例如底部tab按钮封装)

一般情况,有自绘控件,组合控件,继承控件

二.View的滑动

view的滑动分为以下三种:

1)View本身不滚动,指滚动View的内容,这也是View类提供的原始方法,通过scrollToScrollBy方法来实现。scollBy()里面友调用了scollTo()方法

因为ViewViewGroup中的位置是由LayoutParamsmargin等参数决定的,要想滚动View或者说要想改变View的位置只需要改变LayoutParams的相关参数就可以。但是scrollToscrollBy改变的只是mScrollXmScrollY的值(看源码),这两个值对于改变ViewViewGroup里面的位置是毫无关系的

    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();  

            }  

        }  

    }  

      

    public void scrollBy(int x, int y) {  

        scrollTo(mScrollX + x, mScrollY + y);  

    }  


2)使用动画,让View来产生滚动效果

3)通过动态的修改LayoutParamsmargin等属性让View来产生滚动

.如何实现弹性滑动

Scroll类,可能会用到滑动速度跟踪类 VelocityTracker

onTouchEvent里面判断 如果是ACTION_DOWN,就利用

      mScroller.startScroll(getScrollX(), getScrollY(), 0, 200, 1000);让控件向上弹起200像素

ACTION_UP时将200改成-200ok了,当然,每次设置startScroll时得重绘invalidate()重新绘制  触发computeScroll()方法,在里面scrollTo()当前坐标

四.View的滑动冲突

1)、常见的滑动冲突场景

 

场景1——外部滑动方向和内部滑动方向不一致,如:ViewPager中有多个fragment,而fragment中有ListView,这时ViewPager可以左右滑动,而ListView可以上下滑动,这就造成了滑动冲突。注意:这只是举个例子说明一下场景1,事实上ViewPager内部已经处理了这种滑动冲突,在采用ViewPager时,我们无需关注这个问题。

 

场景2——外部滑动方向和内部滑动方向一致。

 

场景3——上述两种场景的嵌套,即共有三层,外层与中层的滑动方向一致,而中层与内层的滑动方向不一致。

2)、滑动冲突的处理规则

 

场景1的处理规则:

1、当用户左右滑动时,让外部的View拦截点击事件,当用户上下滑动时,让内部的View拦截点击事件;

2、判断用户的滑动方向(左右、上下):如果用户手指滑动的水平距离大于垂直距离,则左右滑动,反之,上下滑动;还可以根据角度、速度差来做判断;

 

场景2的处理规则:

无法根据滑动的角度、距离差、速度差来判断,因为场景2内部、外部的滑动方向一致;这时候一般都能在业务上找到突破点,如业务上规定:当处于某种状态需要外部View响应用户的滑动,而处于另一种状态时则需要内部View响应用户的滑动,所以我们可以根据业务的需求得出相应的处理规则。

 

场景3的处理规则:场景1的处理规则和场景2的处理规则一起用。

 

解决方法:

1.外部拦截法:(父容器)

     @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        boolean intercepted = false;

        int x = (int)ev.getX();

        int y = (int)ev.getY();

 

        switch (ev.getAction()){

            //父容器不需要拦截,如果拦截的话,之后的事件就无法给子元素了

            case MotionEvent.ACTION_DOWN:

                intercepted = false;

                break;

 

            //在此判断父容器是否需要拦截

            case MotionEvent.ACTION_MOVE:

                if (父容器需要当前点击事件){

                    intercepted = true;

                }

                //父容器不需要当前的点击事件

                else {

                    intercepted = false;

                }

                break;

            //父容器不需要拦截,如果拦截的话,子元素的onClick事件就无法执行

            case MotionEvent.ACTION_UP:

                intercepted = false;

                break;

            default:

                break;

        }

     //重置手指的起始位置

        mLastXIntercept = x;

        mLastYIntercept = y;

 

        return intercepted;

}

 

2.内部拦截法:(子视图通知父容器不拦截)

    @Override

    public boolean dispatchTouchEvent(MotionEvent ev) {

 

        //获得当前的位置坐标

        int x = (int) ev.getX();

        int y = (int) ev.getY();

 

        switch (ev.getAction()){

            case MotionEvent.ACTION_DOWN:

 

                //通知父容器不要拦截事件

                horizontalScrollLayout.requestDisallowInterceptTouchEvent(true);

                break;

            case MotionEvent.ACTION_MOVE:

 

                if (父容器需要此事件){

                    //通知父容器拦截此事件

                    horizontalScrollLayout.requestDisallowInterceptTouchEvent(false);

                }

                break;

            case MotionEvent.ACTION_UP:

                break;

            default:

                break;

        }

        //重置手指的初始位置

        mLastY = y;

        mLastX = x;

 

        return super.dispatchTouchEvent(ev);

    }


.如何定义控件

1.自定义属性的声明与获取

2.测量onMeasure

3.布局onLayout(ViewGroup)

4.绘制onDraw

5.OnTouchEvent

6.onInterceptTouchEvent(ViewGroup)

 一般就会用到onMeasure()和onDraw()方法

(1)分析需要的自定义属性(2)在res/values/attrs.xml定义声明(3)layout.xml中使用(4)View的构造方法中进行获取(会得到TypeArray,最后记得a.recycle())

 

 

   自定义View有自己的职责,测量自身,决定自身到底需要多大的范围以及自身的样子到底有什么样子(onDraw)

onMeasure  

测量有两个因素决定,一个是测量的模式,一个是测量的值。

测量的模式:

UNSPECIFIED  父容器没有对当前View有任何限制(例如ListView,ScrollView),当前View可以任意取尺寸

EXACTLY     当前的尺寸就是当前View应该取的尺寸

AT_MOST  当前尺寸是当前View能取的最大尺寸

MeasureSpec  代表了一组宽度和高度的要求,由大小和模式组成

最后确定控件大小时,需要判断MeasureSpec的mode,不能直接用MeasureSpec的size。在进行一些逻辑处理以后,调用setMeasureDimension()方法,将测量得到的宽高传进去供layout使用,保存结果

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);

    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

 

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

 

    int measuredHeight, measuredWidth;

 

    if (widthMode == MeasureSpec.EXACTLY) {

        measuredWidth = widthSize;

    } else {

        measuredWidth = SIZE;

    }

 

    if (heightMode == MeasureSpec.EXACTLY) {

        measuredHeight = heightSize;

    } else {

        measuredHeight = SIZE;

    }

 

    setMeasuredDimension(measuredWidth, measuredHeight);

}


requestLayout触发测量,重新布局等,不包括onDraw

 

onLayout 

自定义的view,而不是ViewGroup的话就没有此过程

  (1)决定子View显示的位置(2)如果onMeasure执行多次包含耗时操作的话,        可以移动到此方法

 requestLayout()触发

 

onDraw

1.绘制内容区域 

2.invalidate()UI线程  postInvalidate()子线程重绘

3.熟练 Canvas.drawXXX 方法 drawLine drawRect drawCircle

4.translate roate scale skew  Canvas 变换的方法

5.如过使用了变换的话,记得要save() restore() Canvas的状态

drawText() drawPostText()绘制文本 drawArc drawPath绘制路径

drawBitmap()在指定点绘制位图,绘制动画等

onTouchEvent 处理触摸或者手势,多点触控active point....

 

onInterceptToucEvent

 

最后还有onSaveInstanceState onRestoreInstanceState 保存和恢复信息

例如微信里面的自定义View就用到过。

自定义Prograss ,Activity重建时,View就会调用这两个方法

 

ScaleGestureDetector 要写缩放手势时可以利用系统已有类属性完成

等等....


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值