关闭

Android进阶-纯粹自定义控件二

标签: android控件
122人阅读 评论(0) 收藏 举报
分类:

Android进阶-纯粹自定义控件二

本文来看一下自定义ViewGroup需要注意哪些。
以自定义的一个侧滑菜单为例。
图例:
这里写图片描述


  • 关键点

  • 既然是自定义的ViewGroup, 那么的话,控件的具体内容肯定不是要考虑的事情
  • 这个ViewGroup应考虑的是
    • 我们这个ViewGroup有何特点? -> 子View的行为
    • 如何完成自己的 onMeasure(), onLayout()方法,从而控制子View
    • 如何去实现子View的行为

侧滑菜单

  • 这个ViewGroup所具有的特点为:
    • 拥有两个子View,正常状态下,一个可见,一个不可见
    • 可以通过滑动,来控制子View的显示
    • 可以给外界暴露一个借口,来控制隐藏的View显示

实现关键点

  • 如何得到得到子View的引用,从而实现对子View的控制

    • 首先,在构造方法中是得不到的,构造知识把自己给弄出来了
    • 可以覆写protected void onFinishInflate()
      • 这个方法会在所有的子View被inflate之后调用
      • 因此可以通过ViewGroup.getChildAt(position)来得到子View的引用
  • onMeasure方法

    • protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    • widthMeasureSpec和heightMeasureSpec在前面已经说过,这是View的父View传递给他的测量值
    • 我们可以利用这两个参数,来对子View执行测量
    • 不过,在自定义ViewGroup方法时,一般重写这个方法,因为这个方法主要就是用来测量出(依据父View和布局文件中的参数)子View的宽高
    • 我们可以借用其他的ViewGroup的方法, 因此我们可以通过继承来实现(例如继承FrameLayout)
    • 来看一下系统是如何对View进行测量的(我们也可以这么干)
      • 系统测量依赖 MeasureSpec 的 public static int makeMeasureSpec (int size, int mode)方法
      • size代表在布局文件中的宽高的值, mode代表解析这个宽高的模式
      • mode可取: UNSPECIFIED(wrap_content) EXACTLY(精确规定了大小,例如多少dp) AT_MOST(比如math_parent)
      • 例如: int measureSpecWidth = MeasureSpec.makeMeasureSpec(childViewWidth, MeasureSpec.EXACTLY);
  • 如何使一个View隐藏,一个View展示

    • 这肯定是关于子View如何布局,即我们自定义的这个ViewGroup在最初对子View进行布局时, 一个充满屏幕(父View),一个在屏幕外面
    • 上面的实现应在onLayout()方法中

可以这样实现:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        menuView.layout(-menuWidth, 0, 0, menuView.getMeasuredHeight());  //在外面
        mainView.layout(0, 0, r, b); //充满父View
    }
  • 处理用户滑动,两个View显示的问题

    • 当用户向右滑动是,肯定是要是menuView显示出来

      • 有3中方法可以做到这一点(控制子View的移动显示)
        • layout(l, t, r, b) 很好理解,再布一次局就是了
        • offsetTopAndBottom()和offsetLeftAndRight()
        • scrollTo()
          • 将View的边框向(x, y)滑动, 会引起重绘, 这个x, y并没什么限制, 就向在一张大纸上滑动
          • getScrollX (), 得到已经滑动的距离
    • 使用scrollTo()方法来完成子View的滑动效果

      • 我们滑动的是ViewGroup的边框(屏幕的边框)
      • 由相对论,我们可以得出,我们向右滑动,右边的内容就显示出来了,想左滑动,左边的内容就显示出来了
      • 反正就是,你想把左边的隐藏的menuView给滑动出来, 调用scrollTo()时就得向左滑!(得给负值)
    • 即我们在触摸滑动中可以这样处理
      如下

      //向右滑slideLength 是正的, 向左滑slideLength是负的
      int newScrollX = getScrollX() - slideLength;      //上次滑动的距离 - 这次滑动的距离
      if(newScrollX<-menuWidth)
          newScrollX = -menuWidth;     //最大滑动距离, 不能超出menuView的宽
      if(newScrollX>0)newScrollX = 0;  //     不能向左滑
      scrollTo(newScrollX, 0);      
      
  • 暴露出给用户直接显示menuView和关闭menuView的接口

    • 其实这两个接口,就是能够使用户通过调用这两个接口来完成menuView的显示
    • 直接实现的话很简单, 就向上面的代码, 滑出来就是
    • 但是, 一次滑动到指定位置,太快,用户体验不好
    • 因此我们需要,让menuView在一定的时间内滑动出来哦
    • 实现这个问题,共有两种办法

1)使用自定义动画,

/**
 * 这个动画让指定view在一段时间内scrollTo到指定位置
 */
    public class ScrollAnimation extends Animation{

        private View view;
        private int targetScrollX;
        private int startScrollX;
        private int totalValue;

        public ScrollAnimation(View view, int targetScrollX) {
            super();
            this.view = view;
            this.targetScrollX = targetScrollX;

            startScrollX = view.getScrollX();
            totalValue = this.targetScrollX - startScrollX;

            int time = Math.abs(totalValue);  
            setDuration(time);      //实现,依据滑动的距离,来决定滑动动画时间的长与短
        }
    /**
     * 在指定的时间内一直执行该方法,直到动画结束
     * interpolatedTime:0-1  标识动画执行的进度或者百分比
     * 当前的值 = 起始值 + 总的差值*interpolatedTime
     */
        @Override
        protected void applyTransformation(float interpolatedTime,
                Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            int currentScrollX = (int) (startScrollX + totalValue*interpolatedTime);
            view.scrollTo(currentScrollX, 0);
        }
    }

2)利用Scroller, 它可以模拟一个移动的过程,但并不会是View真正移动

    private void closeMenu(){
        scroller.startScroll(getScrollX(), 0, 0-getScrollX(), 0, 400);
        invalidate();
    }

    private void openMenu(){
        scroller.startScroll(getScrollX(), 0, -menuWidth-getScrollX(), 0, 400);
        invalidate();
    }
/**
 * Scroller不主动去调用这个方法
 * 而invalidate()可以掉这个方法
 * invalidate->draw->computeScroll
 */
    @Override
    public void computeScroll() {  //这个方法
        super.computeScroll();
        if(scroller.computeScrollOffset()){//返回true,表示动画没结束, 当计算的滑动的值到我们模拟滑动的地方就停止
            scrollTo(scroller.getCurrX(), 0);
            invalidate();   
        }
    }
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:57728次
    • 积分:2359
    • 等级:
    • 排名:第16344名
    • 原创:179篇
    • 转载:27篇
    • 译文:0篇
    • 评论:3条
    最新评论