Android 开发实践 ViewGroup 实现左右滑出窗口(一)
利用假期把以前做的东西总结整理一下,先从简单的开始吧。
实现的效果是这样的:
做了个截屏动画,比例有点不对了,凑合着看吧。
整个窗口有3部分组成,中间的主界面是个列表,左边的滑出界面是个菜单,右边的滑出界面是编辑框等。左边菜单是半屏的,右边的是全屏的。
最后其实我只用到了左边的滑出窗口,但还是把左右的算法都做全了。
并且设计了一个方法,让activity可以指定两个滑出窗口的宽度。
源码: http://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的宽度。
public PushSlider(Context context, AttributeSet attrs) {
super(context, attrs);
setHapticFeedbackEnabled(false);
mContext = context;
mForbidden = false;
mGDetector = new GestureDetector(mContext,this);
focusedIndex = SLIDER_PAGE_MIDDLE;
mMoveFlag = MOVE_FLAG_ALLOW_LEFT|MOVE_FLAG_ALLOW_RIGHT;
mDensity = getResources().getDisplayMetrics().density;
mScroller = new Scroller(mContext, new AccelerateInterpolator());
mChildWidth = new int[3];
}
初始化数据:
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方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if(mChild == null){
initChildren(widthSize,heightSize);
}
else{
int newSpec = MeasureSpec.makeMeasureSpec(mChildWidth[focusedIndex], MeasureSpec.EXACTLY);
mChild[focusedIndex].measure(newSpec, heightMeasureSpec);
}
}
入参是父view的尺寸,第7行第一次执行onMeasure时会根据父view的宽和高 (这里等于屏的宽和高) 来初始化子view。
private void initChildren(int width, int height){
if(mChild == null){
mChild = new View[3];
mChildWidth[SLIDER_PAGE_FIX]=width;
}
for(int i=SLIDER_PAGE_LEFT; i<=SLIDER_PAGE_RIGHT; i++){
mChild[i] = getChildAt(i);
if(mChild[i] != null){
mChild[i].setVisibility(View.VISIBLE);
int childWidth = mChildWidth[i];
if(childWidth > width){
childWidth = width;
mChildWidth[i] = width;
}
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
mChild[i].measure(widthMeasureSpec, heightMeasureSpec);
}
}
changeFocus();
}
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
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { if(changed){ if(mChild[focusedIndex] != null){
int w = mChildWidth[focusedIndex];
if(w == 0){
w = r-l;
}
mChild[focusedIndex].layout(l,t,l+w,b);
} for(int next=SLIDER_PAGE_LEFT; next<=SLIDER_PAGE_RIGHT; next++){
if(next == focusedIndex){
continue;
}
if(mChild[next] != null){
int w1 = mChildWidth[next];
int x = 0;
if((next== SLIDER_PAGE_LEFT)&&(focusedIndex == SLIDER_PAGE_MIDDLE)){
x = l- w1;
}else if((next== SLIDER_PAGE_LEFT)&&(focusedIndex == SLIDER_PAGE_RIGHT)){
x = l- mChildWidth[SLIDER_PAGE_MIDDLE] - w1;
}else if((next== SLIDER_PAGE_MIDDLE)&&(focusedIndex == SLIDER_PAGE_LEFT)){
x = l+ mChildWidth[SLIDER_PAGE_LEFT];
}else if((next== SLIDER_PAGE_MIDDLE)&&(focusedIndex == SLIDER_PAGE_RIGHT)){
x = l-w1;
}else if((next== SLIDER_PAGE_RIGHT)&&(focusedIndex == SLIDER_PAGE_MIDDLE)){
x = l+mChildWidth[SLIDER_PAGE_MIDDLE];
}
else if((next== SLIDER_PAGE_RIGHT)&&(focusedIndex == SLIDER_PAGE_LEFT)){
x = l+mChildWidth[SLIDER_PAGE_LEFT]+mChildWidth[SLIDER_PAGE_MIDDLE];
}
mChild[next].layout(x,t,x+w1,b);
}
} mLeftPosX = -mChildWidth[SLIDER_PAGE_LEFT];
if(mChild[SLIDER_PAGE_RIGHT] != null)
mRightPosX = mChildWidth[SLIDER_PAGE_MIDDLE];
else
mRightPosX = 0;
}else{ if(mChild[focusedIndex] != null){
int w = mChildWidth[focusedIndex];
mChild[focusedIndex].layout(l,t,l+w,b);
}
}
}
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>
Android 开发实践 ViewGroup 实现左右滑出窗口(一)的更多相关文章
- Android 开发实践 ViewGroup 实现左右滑出窗口(二)
接上一篇 <Android 开发实践 ViewGroup 实现左右滑出窗口(一)http://www.cnblogs.com/inkheart0124/p/3532862.html> 源码 ...
- Xamarin.Android开发实践(一)
原文:Xamarin.Android开发实践(一) 一.准备工作 1.创建一个空的解决方案,并命名为Phoneword 2.右击解决方案 新建->新建项目 并命名为Phoneword_Droid ...
- Xamarin.Android开发实践(五)
原文:Xamarin.Android开发实践(五) 一.服务的生命周期 服务与活动一样,在它的整个生命周期中存在着一些事件,下图可以很好解释整个过程以及涉及到的方法: 在真实的使用中,Service来 ...
- Xamarin.Android开发实践(四)
原文:Xamarin.Android开发实践(四) Xamarin.Android下获取与解析JSON 一.新建项目 1.新建一个Android项目,并命名为为NetJsonList 2.右击引用,选 ...
- Xamarin.Android开发实践(三)
原文:Xamarin.Android开发实践(三) 一.前言 用过Android手机的人一定会发现一种现象,当你把一个应用置于后台后,一段时间之后在打开就会发现应用重新打开了,但是之前的相关的数据却没 ...
- Xamarin.Android开发实践(二)
原文:Xamarin.Android开发实践(二) 一.准备 开始学习本教程前必须先完成该教程http://www.cnblogs.com/yaozhenfa/p/xamarin_android_qu ...
- [Android Pro] Android开发实践:自定义ViewGroup的onLayout()分析
reference to : http://www.linuxidc.com/Linux/2014-12/110165.htm 前一篇文章主要讲了自定义View为什么要重载onMeasure()方法( ...
- Android开发技巧——使用PopupWindow实现弹出菜单
在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...
- Xamarin.Android开发实践(九)
Xamarin.Android之ActionBar与菜单 一.选项卡 如今很多应用都会使用碎片以便在同一个活动中能够显示多个不同的视图.在 Android 3.0 以上的版本中,我们已经可以使用Act ...
随机推荐
- show
showproperties thefrm.Controls --显示属性??showmethods thefrm.Menu---显示功能?? showclass "*bitmap*&quo ...
- applicationContext.xml详解 spring+mybatis+struts
今天给大家详细解释一项关于Spring的applicationContext.xml文件,这对于初学者来说,应该是很有帮助的, 以下是详解Spring的applicationContext.xml文件 ...
- 【产品体验】支付宝Alipay9.0
自己摸索中也要学习别人的分析,生命不息,学习不止~~ 支付宝9.0新界面如下图所示——Logo变了,上方突出了“附近”入口,下方新增了“商家”“朋友”两个一级tab,新增了亲情账户,财富界面进行了改 ...
- 用word2013写博客
第一次使用,添加博客账户时碰到一个奇怪的问题,先输入用户名,然后密码只能输入两个字符,后来先输入密码再输入用户名才解决,很神奇~
- 高性能页面加载技术(流水线加载)BigPipe的C#简单实现(附源码)
一,BigPipe简介 BigPipe是一个重新设计的基础动态网页服务体系.大体思路是,分解网页成叫做Pagelets的小块,然后通过Web服务器和浏览器建立管道并管理他们在不同阶段的运行.这是类似于 ...
- sqlserver 字符串处理函数解释
1.ASCII()返回字符表达式最左端字符的ASCII 码值.在ASCII()函数中,纯数字的字符串可不用‘’括起来,但含其它字符的字符串必须用‘’括起来使用,否则会出错.2.CHAR()将ASCII ...
- Contest20140705 testA 二分
testA 输入文件: testA.in 输出文件testA.out 时限2000ms 问题描述: 有一个城市拥有N个节点,被M条有权无向路径连接.现在你要在一个地方(可以在路径上当然也可以在节点上) ...
- Android listView scroll 恢复滚动位置
相信大家尝试过许多方法恢复滚动位置,本人也找了许多方法,唯有这个方法好用,下面把代码贴出来 声明两个变量 private int mPosition; private int lvChildTop; ...
- 传智播客C语言视频第二季(第一季基础上增加诸多C语言案例讲解,有效下载期为10.5-10.10关闭)
卷 backup 的文件夹 PATH 列表卷序列号为 00000025 D4A8:14B0J:.│ 1.txt│ c语言经典案例效果图示.doc│ ├─1传智播客_尹成_C语言从菜鸟到高手_第一 ...
- Linux下的各种软件安装方法汇总
1 RPM包 1.1 安装 RPM包就像Windows的EXE安装文件一样,各种文件都已经编译好了,并进行了打包,哪一个文件应该放在哪一个目录下都指定好了,安装非常方便,在图形界面里你只需要双击就能自 ...