• Android 开发实践 ViewGroup 实现左右滑出窗口(一)


    利用假期把以前做的东西总结整理一下,先从简单的开始吧。
    实现的效果是这样的:

      做了个截屏动画,比例有点不对了,凑合着看吧。

    整个窗口有3部分组成,中间的主界面是个列表,左边的滑出界面是个菜单,右边的滑出界面是编辑框等。左边菜单是半屏的,右边的是全屏的。
    最后其实我只用到了左边的滑出窗口,但还是把左右的算法都做全了。
    并且设计了一个方法,让activity可以指定两个滑出窗口的宽度。

     源码: https://files.cnblogs.com/inkheart0124/PushSlider.zip

    一、自定义PushSlider类 继承ViewGroup 并实现手势listener

    PushSlider.java

    public class PushSlider extends ViewGroup implements OnGestureListener
    public static final int SLIDER_PAGE_LEFT = 0;
    public static final int SLIDER_PAGE_MIDDLE = 1;
    public static final int SLIDER_PAGE_RIGHT = 2;

    三个Child View的index,其中SLIDER_PAGE_MIDDLE是定宽的(全屏宽度),SLIDER_PAGE_LEFT和SLIDER_PAGE_RIGHT可以设置View的宽度。

     1 public PushSlider(Context context, AttributeSet attrs) {
     2     super(context, attrs);
     3     setHapticFeedbackEnabled(false);
     4     mContext = context;
     5     mForbidden = false;
     6     mGDetector = new GestureDetector(mContext,this);
     7     focusedIndex = SLIDER_PAGE_MIDDLE;
     8     mMoveFlag = MOVE_FLAG_ALLOW_LEFT|MOVE_FLAG_ALLOW_RIGHT;
     9     mDensity = getResources().getDisplayMetrics().density;
    10     mScroller = new Scroller(mContext, new AccelerateInterpolator());
    11     mChildWidth = new int[3];
    12         
    13     }

    初始化数据:

    5行,mForbidden = false; 初始状态默认允许滑动,activity可以通过mPushView.pushForbid(true)方法禁止滑动。

    6行,mGDetector = new GestureDetector(mContext,this); 创建手势监听对象。

    7行,初始focus的view。

    9行,获取像素密度,后面要用它来计算位置。

    10行,Scroller是一个实现View group平滑滚动的一个helper类。mScroller和mGDetector就是后面手势滑动的主角。

    11行,数组mChildWidth[] 记录每个子view的宽度

    重写onMeasure和onLayout方法:

     1 @Override
     2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
     3     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     4     int widthSize = MeasureSpec.getSize(widthMeasureSpec);
     5     int heightSize = MeasureSpec.getSize(heightMeasureSpec);
     6     if(mChild == null){
     7         initChildren(widthSize,heightSize);
     8     }
     9     else{
    10         int newSpec = MeasureSpec.makeMeasureSpec(mChildWidth[focusedIndex], MeasureSpec.EXACTLY);
    11         mChild[focusedIndex].measure(newSpec, heightMeasureSpec);
    12     }
    13 }      

    入参是父view的尺寸,第7行第一次执行onMeasure时会根据父view的宽和高 (这里等于屏的宽和高) 来初始化子view。

     1 private void initChildren(int width, int height){
     2     if(mChild == null){
     3         mChild = new View[3];
     4         mChildWidth[SLIDER_PAGE_FIX]=width;
     5     }
     6         
     7     for(int i=SLIDER_PAGE_LEFT; i<=SLIDER_PAGE_RIGHT; i++){
     8         mChild[i] = getChildAt(i);
     9         if(mChild[i] != null){
    10             mChild[i].setVisibility(View.VISIBLE);
    11             int childWidth = mChildWidth[i];
    12             if(childWidth > width){
    13                 childWidth = width;
    14                 mChildWidth[i] = width;
    15             }
    16             int widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
    17             int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
    18             mChild[i].measure(widthMeasureSpec, heightMeasureSpec);
    19         }
    20     }        
    21     changeFocus();        
    22 }            

    initChildren方法 初始化数组mChild,记录每个子view的对象。

    4行,定宽的那个子view直接设置成全屏宽度。定宽的是索引为SLIDER_PAGE_MIDDLE的子view。

    7~19行,getChildAt(i)获得ViewGroup中的每个子View,根据每个子view的宽度设置调用它们的measure方法。

    因此,主activity必须在onCreate方法中执行pushSlider的setPageWidth方法,设置左右两个子view的宽度。

               mPushView.setPageWidth(PushSlider.SLIDER_PAGE_LEFT, screenWidth/2);
               mPushView.setPageWidth(PushSlider.SLIDER_PAGE_RIGHT, screenWidth);

    public void setPageWidth(int index, int width){
        if(index == SLIDER_PAGE_FIX)
        return;
            
        mChildWidth[index]=width;
    }

    onLayout

     1 @Override
     2 protected void onLayout(boolean changed, int l, int t, int r, int b) {
     3 
     4     if(changed){
     5 
     6         if(mChild[focusedIndex] != null){
     7             int w = mChildWidth[focusedIndex];
     8             if(w == 0){
     9                 w = r-l;
    10             }
    11             mChild[focusedIndex].layout(l,t,l+w,b);
    12         }
    13             
    14         for(int next=SLIDER_PAGE_LEFT; next<=SLIDER_PAGE_RIGHT; next++){
    15             if(next == focusedIndex){
    16                 continue;
    17             }
    18             if(mChild[next] != null){
    19                 int w1 = mChildWidth[next];
    20                 int x = 0;
    21                 if((next== SLIDER_PAGE_LEFT)&&(focusedIndex == SLIDER_PAGE_MIDDLE)){
    22                     x = l- w1;
    23                 }else if((next== SLIDER_PAGE_LEFT)&&(focusedIndex == SLIDER_PAGE_RIGHT)){
    24                     x = l- mChildWidth[SLIDER_PAGE_MIDDLE] - w1;
    25                 }else if((next== SLIDER_PAGE_MIDDLE)&&(focusedIndex == SLIDER_PAGE_LEFT)){
    26                     x = l+ mChildWidth[SLIDER_PAGE_LEFT];
    27                 }else if((next== SLIDER_PAGE_MIDDLE)&&(focusedIndex == SLIDER_PAGE_RIGHT)){
    28                     x = l-w1;
    29                 }else if((next== SLIDER_PAGE_RIGHT)&&(focusedIndex == SLIDER_PAGE_MIDDLE)){
    30                     x = l+mChildWidth[SLIDER_PAGE_MIDDLE];
    31                 }
    32                else if((next== SLIDER_PAGE_RIGHT)&&(focusedIndex == SLIDER_PAGE_LEFT)){
    33                     x = l+mChildWidth[SLIDER_PAGE_LEFT]+mChildWidth[SLIDER_PAGE_MIDDLE];
    34                 }
    35                 mChild[next].layout(x,t,x+w1,b);
    36             }
    37         }
    38             
    39         mLeftPosX = -mChildWidth[SLIDER_PAGE_LEFT];
    40         if(mChild[SLIDER_PAGE_RIGHT] != null)
    41             mRightPosX = mChildWidth[SLIDER_PAGE_MIDDLE];
    42         else
    43             mRightPosX = 0;
    44     }else{
    45 
    46         if(mChild[focusedIndex] != null){
    47             int w = mChildWidth[focusedIndex];
    48             mChild[focusedIndex].layout(l,t,l+w,b);
    49         }
    50     }
    51 }    

    onLayout 没什么特别的,就是把每个子view布局一下,调用子view的layout(l,t,r,b)方法。

    以我的手机屏宽720pixels,中间的view从l=0到r=719,左边的就是-359到0,右边的720到1439。

    =================================================================

    这两个函数看起来很简单,无非是算位置烦一点。但是在这里我犯了一个错误,导致后面花了很多时间找原因。

    我的menifest里是这样的

    <activity
      android:name=".MainActivity"
      android:label="@string/activity_car"
      android:screenOrientation="portrait"
      android:configChanges="orientation|keyboardHidden"
    >

    只支持竖屏。当时的考虑是,对pushSlider来说,onMeasure只需在第一次执行时measure每个子view就可以了,因为子view的尺寸是不会变化的。而onLayout也只需要在changed == true的时候layout子view,其位置相对于父view来说也是固定的。(scroller滚动的是整个父view相对屏的位置)

    但是,我的middle view中布局了一个ListView,于是悲剧了。我发现list动态修改item后不会刷新,调用mAdapter.notifyDataSetChanged()后也不刷新。

    各种猜测以及试验以及百度了很久,最后发现ListView的条目发生改变后,父view的onMeasure和onLayout会被调用,这里必须调用list所在view的measure和layout方法,否则就会导致上面的不刷新问题。

    =================================================================

    这样三个子view的布局就ok了。在开始滑动之前,还需要定义个监听接口,让activity中能监听到滑动后子view的焦点切换情况。

    private OnPageChangedListener mPageChangedListener = null;
        
    public interface OnPageChangedListener{
        void onPageChanged(View v, int whichHandle);
    }
        
    public void setOnPageChangedListener(OnPageChangedListener listener){
        mPageChangedListener = listener;
    }

    activity需要调用mPushView.setOnPageChangedListener(this);注册监听,并实现void onPageChanged(View v, int whichHandle),两个传参分别是获得焦点的子view和其索引值。

    手势滑动部分请参考 <Android 开发实践 ViewGroup 实现左右滑出窗口(二)http://www.cnblogs.com/inkheart0124/p/3534165.html>

  • 相关阅读:
    饮冰三年人工智能Pandas74初始Pandas
    数据库中sql执行顺序是什么?
    【解决了一个小问题】vmagent中,如何对envoy这样的特殊expoter路径做处理?
    APISIX配置
    MySQL在线DDL工具 ghost
    ghost测试
    ghost工具在线改表过程的详细解析
    js按条件截取字符串
    quilleditor复制图片有base64转为地址上传
    vue的事件总线BUS
  • 原文地址:https://www.cnblogs.com/inkheart0124/p/3532862.html
Copyright © 2020-2023  润新知