Android 开发实践 ViewGroup 实现左右滑出窗口(一)
利用假期把以前做的东西总结整理一下,先从简单的开始吧。
实现的效果是这样的:
做了个截屏动画,比例有点不对了,凑合着看吧。
整个窗口有3部分组成,中间的主界面是个列表,左边的滑出界面是个菜单,右边的滑出界面是编辑框等。左边菜单是半屏的,右边的是全屏的。
最后其实我只用到了左边的滑出窗口,但还是把左右的算法都做全了。
并且设计了一个方法,让activity可以指定两个滑出窗口的宽度。
一、自定义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 实现左右滑出窗口(二)>
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。