美文网首页android技术收藏Android开发摘录自定义控件
Android-教你写小米系统应用--"我的小米&qu

Android-教你写小米系统应用--"我的小米&qu

作者: 三好码农 | 来源:发表于2015-04-30 15:44 被阅读1446次

    我承认我有点标题党了,我不可能完整的介绍怎么写小米应用,我这篇要说的其实是模仿MIUI6系统应用“我的小米”的首页,主要实现的UI是一个圆形的头像,下面是用户名,再下面是一些功能的cell,然后向上滑动功能cell,可以将头像渐隐,然后用户名放大放到页面顶部,向下滑动,恢复页面初始样貌,大家如果手头有小米手机的可以自己感受下(我自己觉得小米的一些系统应用做的还是不错的)。

    构思

    前面的文章中,我们已经了解了如何去自定义一个ViewGroup,可以在onLayout中自由的对子View进行位置设定,我们今天这里刚好需要对上面需求提到的三部分子View(头像ImageView,姓名TextView,功能Cell布局)在滑动过程中进行位置设定,重绘,所以我们就可以自定义一个ViewGroup去实现。

    public class MineMiView extends ViewGroup {
        public MineMiView(Context context) {
        super(context, null, 0);
      }
    
      public MineMiView(Context context, AttributeSet attrs) {
        super(context, attrs, 0);
      }
    
      public MineMiView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
      }
    
      @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
      }
    }
    

    我们接着将主布局文件画一下,现在我们为了简单起见,布局的第三部分-功能cell部分暂时先用一个空的天蓝色LinearLayout布局代替,后面我们会替换回去。

     <com.example.aliouswang.myapplication.widget.MineMiView
        android:id="@+id/mineMiView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    
        <ImageView
            android:id="@+id/head_imageview"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:background="@drawable/default_head"
            ></ImageView>
    
        <LinearLayout
            android:id="@+id/username_rootview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:gravity="center_horizontal"
            >
            <TextView
                android:id="@+id/name_tv1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Joe少"
                android:textSize="16sp"
                android:textColor="@android:color/holo_orange_dark"
                ></TextView>
    
            <TextView
                android:id="@+id/name_tv2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="stay hungry, stay foolish"
                android:textSize="12sp"
    android:textColor="@android:color/holo_orange_light"
                ></TextView>
            </LinearLayout>
                <LinearLayout
                    android:id="@+id/content_rootview"
                    android:orientation="vertical"
       android:background="@android:color/holo_blue_light"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
    
                </LinearLayout>
     </com.example.aliouswang.myapplication.widget.MineMiView>
    
    //初始化我们即将使用的三个子View
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mHeadImageView = (ImageView)findViewById(R.id.head_imageview);
        mUserNameRootView = (LinearLayout)findViewById(R.id.username_rootview);
        mContentRootView = (MiScrollView)findViewById(R.id.content_rootview);
    }
    
        @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        sWidth = MeasureSpec.getSize(widthMeasureSpec);
        sHeight = MeasureSpec.getSize(heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        //measure头像控件的大小
        headImageWidth = mHeadImageView.getMeasuredWidth();
        headImageHeight = mHeadImageView.getMeasuredHeight();
        //measure姓名控件的大小
        userNameWidth = mUserNameRootView.getMeasuredWidth();
        userNameHeight = mUserNameRootView.getMeasuredHeight();
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mHeadImageView.layout((sWidth - headImageWidth)/2, mMarginTop
        ,(sWidth + headImageWidth)/2, mMarginTop + headImageHeight
                );
        //计算第三部分content的高度,其他控件的位置根据它的高度来设置
        if (contentHeight == -1) {
            contentHeight = sHeight - (mMarginTop + headImageHeight +
                    50 + userNameHeight);
        }
    
        mUserNameRootView.layout((sWidth - userNameWidth)/2,
                sHeight - contentHeight - 50 - userNameHeight
                ,(sWidth + userNameWidth)/2,
                sHeight - contentHeight - 50
        );
    
        mContentRootView.layout(0, sHeight - contentHeight
                ,sWidth, sHeight
        );
        
       //初始化MaxTop,MinTop, currentTop.
        if (maxTop == 0) {
            maxTop = mMarginTop + headImageHeight + 80 + userNameHeight;
            minTop = mMarginTop + 30;
            currentTop = maxTop;
        }
    
    }
    

    实现滑动-ViewDragHelper

    滑动子View可以通过监听ACTION_MOVE事件然后不断layout子View在ViewGroup中的位置实现滚动,但是如果完全自己写,逻辑就比较复杂了。其实Android support V4架包已经为我们提供了ViewDragHelper类,来辅助我们在自定义ViewGroup时,来处理子View的滑动需求。下面我们就简单介绍下ViewDragHelper的用法。

    • 1.我们先实例化一个ViewDragHelper对象mDragHelper,可以在onAttachedToWindow()方法中初始化,也可以在ViewGroup的构造器中初始化。It's depend on you.

      @Override
      protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mDragHelper = ViewDragHelper.create(this, 1.0f, new MiDragHelperCallback());
        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
      }
      
      private class MiDragHelperCallback extends ViewDragHelper.Callback {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == mContentRootView;
        }
      }
      

    ViewDragHelper.Callback是一个抽象类,里面定义了很多回调方法,我们这里只说明我们用到的方法,其他的留给大家自己深入了解学习。

    //是否允许抓取View,即你手指在屏幕上触摸拖动的View child是否允许被拖动
    //我们这里只允许子View mContentRootView拖动。
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
      return child == mContentRootView;
    }
    
    //拖动的子View水平方向的位置,这里其实给我们一次修改被拖动的子View水平位置的机会,我们根据需求返回值
    //因为我们这里只处理Vertical方向拖动,Horizontal方向的返回0即可。
    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
            return 0;
    }
    
    //与上面Horizontal类似,我们对Vertical方向的滑动位置控制在minTop和maxTop之间,这二个参数可以根据需求设置
    //另外有一个topBounusFator用来表示上下滑动的弹性系数,滑动超出后弹回正确的位置,
    @Override
      public int clampViewPositionVertical(View child, int top, int dy) {
          int resultTop = top;
          if (resultTop > maxTop + topBonunsFator) {
              resultTop = maxTop + topBonunsFator;
          }
          if (resultTop < minTop - topBonunsFator){
              resultTop = minTop - topBonunsFator;
          }
          return resultTop;
      }
    
    //当子View的位置即将发生改变时,这里给了我们修改layout子View 的位置的机会,
    //同时我们根据滑动的位置,还设置了mHeadImageView的透明度和 mUserNameRootView的缩放系数
    //最后调用requestLayout
      @Override
      public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            if (changedView == mContentRootView) {
                if (currentTop == top) {
                    bMoved = false;
                    return;
                }else {
                    bMoved = true;
                }
                contentHeight = sHeight - top;
                contentScale = ((float)(maxTop - top)/(float)(maxTop - minTop) * scaleFator) + 1;
                headImageAlpha = 1 - (float)(maxTop - top) * alphaFator/(float)(maxTop - minTop);
                if (headImageAlpha < 0) {
                    headImageAlpha = 0;
                }
                currentTop = top;
                mUserNameRootView.setScaleX(contentScale);
                mUserNameRootView.setScaleY(contentScale);
                mHeadImageView.setAlpha(headImageAlpha);
                requestLayout();
            }
      }    
    
      //当拖动的子View释放后,即手指离开屏幕后,这里我们对滑动的速度和手指的最后位置进行判断,
      //通过判断最后滑动到Top或者Bottom,通过调用mDragHelper.settleCapturedViewAt(0, maxTop);
      //注意最后需要手动刷新ViewCompat.postInvalidateOnAnimation(MineMiView.this);
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if (yvel < -defaultVotical) {
                mDragHelper.settleCapturedViewAt(0, minTop);
            }else if(yvel > defaultVotical){
                mDragHelper.settleCapturedViewAt(0, maxTop);
            }else if(currentTop < (minTop + maxTop) /2) {
                mDragHelper.settleCapturedViewAt(0, minTop);
            }else {
                mDragHelper.settleCapturedViewAt(0, maxTop);
    
            }
            ViewCompat.postInvalidateOnAnimation(MineMiView.this);
        }
    

    传递MotionEvent给ViewDragHelper

    上面已经将ViewDragHelper初始化完毕,那么现在就需要将MineMiView的触摸事件传递给mDragHelper,其实跟利用GestureDetector来处理手势事件是类似的。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_CANCEL
                || ev.getAction() == MotionEvent.ACTION_UP) {
            mDragHelper.cancel();
            return super.onInterceptTouchEvent(ev);
        }
        boolean val = mDragHelper.shouldInterceptTouchEvent(ev);
        return val || super.onInterceptTouchEvent(ev);
    } 
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }
    

    最后还有一点工作要做,因为我们知道手指在屏幕上滑动时,会有一个加速度,我们希望做一个减速过程来结束ViewDragHelper的settle,我们可以在computeScroll方法中做处理。

      @Override
      public void computeScroll() {
       //判断mDragHelper的settle是否结束,未结束,继续刷新ViewGroup
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
      }
    

    其实我们的骨干代码就基本完成了,我们来看下运行效果。

    mine_mi_gif.gif

    总结

    我们利用ViewDragHelper简化了我们处理View拖动的逻辑,但是我们现在还不完善,因为,如果我们蓝色的contentView与它的子View被设置了点击事件,那么MotionEvent就不会传递到我们的MineMiView,也就不会传给我们的ViewDragHelper来处理,这时我们上面的代码肯定不能正常工作,所以我们需要额外做触摸事件的拦截,即只有当contentView符合drag的条件时,我们认为应该让MineMiView拦截事件(onInterceptTouchEvent 返回true即可),然后传递给ViewDragHelper处理来实现滑动,否则触摸事件交给子View消费。这一块具体的实现过程,我后面再出一篇详细介绍。

    相关文章

      网友评论

      • Blur丶:可以把demo 放一下到github吗

      本文标题:Android-教你写小米系统应用--"我的小米&qu

      本文链接:https://www.haomeiwen.com/subject/tzxbfttx.html