Android自定义View之侧边栏初探
来源:互联网 发布:大数据 涂子沛在线阅读 编辑:程序博客网 时间:2024/06/07 20:27
前面写了几篇关于进程/线程原理的;感觉和应用层关系不大;但是实际上,写代码和做事情一样,都注重的是一个严谨的思维习惯;如果你真的深刻的理解了底层进程/线程交互模型,内存管理等;那么对你做APP也是有很大帮助的;比如为进程分配资源的时候,系统的一套思路就很值得借鉴;就能够避免应用中的内存问题;而管理多线程的思路更是值得在开发中借鉴。好了废话不多说;现在回到本章的主题,Android中从侧边栏的实现看自定义View;
首先我们知道,Android给应用层提供的组件有ViewFroup和VIew;其中ViewGroup是继承于View的;View相当于一个页面,但是ViewGroup可以容纳很多View;
现在分析下侧边栏的实现,侧边栏有2个页面,一个是菜单栏,一个是展示内容的页面;这两个页面都容纳在一个ViewGroup中;因此实际上侧边栏是由三个ViewGroup组成,背景是个ViewGroup,容纳了两个子ViewGroup,一个装载菜单,一个装载内容页面;
侧边栏有手势判断事件,这个可能是里面的难点;关于事件传递机制,可以参考
http://blog.csdn.net/jike0901xuye/article/details/47616051这一篇;然后侧边栏还有一个是平移效果,这个估计也难倒了好多人;本篇博客会给你一个通用的,不复杂的解决方案;
首先我们继承一个ViewGroup:定义所需要的对象;注意Scroller,这个就是我用来实现平移动画的类;
public class Sidebarview extends RelativeLayout { /** * @author 徐晔 * @note 侧边栏需要实现需要的配置文件 */ public static interface SidebarConfig { /**边栏所占用的百分比*/ public int setSidebarpercent(); /**左边还是右边*/ public int setSidebarPosition(); } /** 元素 */ private class SideElements { /** 上下文*/ private Context mContext; /** 屏幕宽度 */ private int screenwidth; /** 菜单页面所在的页面*/ private RelativeLayout mMenuLayout; /** 侧栏所在的页面 */ private RelativeLayout mSideLayout; /** 配置文件 */ private SidebarConfig mConfig; /**触摸时需要的临时文件 */ private int menuspace; private int mMotionX, mdeteX; public void setmMotionX(int mMotionX) { this.mMotionX = mMotionX; } } /** 元素*/ private SideElements mElements; /** 平移动画实现类 */ private Scroller mScroller; public void setElements(int setnumber) { mElements.setmMotionX(setnumber); }
然后实现必须实现的方法:
public Sidebarview(Context context, SidebarConfig sidebarConfig) { super(context); mElements = new SideElements(); initView(context, sidebarConfig); } public Sidebarview(Context context, AttributeSet attrs, SidebarConfig sidebarConfig) { super(context, attrs); mElements = new SideElements(); initView(context, sidebarConfig); } public Sidebarview(Context context, AttributeSet attrs, int defStyle, SidebarConfig sidebarConfig) { super(context, attrs, defStyle); mElements = new SideElements(); initView(context, sidebarConfig); }
然后看重点1:将两个子ViewGroup添加进入:注意computeScroll方法,该方法在界面绘制的时候会调用;也就是只要界面有变动,就会调用该方法;所以该方法适合在平移时和用手拖动页面时,对别的View进行一个相关联的变化;
/** 初始化组件 */ private void initView(Context context, SidebarConfig sidebarConfig) { mScroller = new Scroller(context); //设置 mElements.mContext = context; mElements.screenwidth = Utils.getScreenSize(context)[0]; mElements.mConfig = sidebarConfig; this.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); // 添加菜单页面 mElements.mMenuLayout = new RelativeLayout(context); mElements.mMenuLayout.setId(0); mElements.mMenuLayout.setTag("sidemenu"); LayoutParams mMenuParams = new LayoutParams(mElements.screenwidth * ((100 - mElements.mConfig.setSidebarpercent())) / 100, LayoutParams.MATCH_PARENT); mElements.mMenuLayout.setLayoutParams(mMenuParams); addView(mElements.mMenuLayout); // 添加侧边栏页面 mElements.mSideLayout = new RelativeLayout(context) { /** 平移动画 */ @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { mElements.mSideLayout.scrollTo(mScroller.getCurrX(), 0); mElements.mSideLayout.postInvalidate(); } if (mElements.mConfig.setSidebarPosition() % 2 == 0) { if (mElements.mSideLayout.getScrollX() == mElements.menuspace) { mElements.mMenuLayout.scrollTo(0, 0); return; } if (mElements.mSideLayout.getScrollX() == 0) { mElements.mMenuLayout.scrollTo( -mElements.menuspace >> 1, 0); return; } mElements.mMenuLayout.scrollTo( (-mElements.menuspace + mElements.mSideLayout .getScrollX()) >> 1, 0); } if (mElements.mConfig.setSidebarPosition() % 2 == 1) { if (mElements.mSideLayout.getScrollX() == 0) { mElements.mMenuLayout.scrollTo( mElements.menuspace >> 1, 0); return; } if (mElements.mSideLayout.getScrollX() == mElements.menuspace) { mElements.mMenuLayout.scrollTo(0, 0); return; } mElements.mMenuLayout.scrollTo( (mElements.menuspace + mElements.mSideLayout .getScrollX()) >> 1, 0); } super.computeScroll(); } }; mElements.mSideLayout.setId(1); mElements.mSideLayout.setTag("sideside"); LayoutParams mSideParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); mElements.mSideLayout.setLayoutParams(mSideParams); addView(mElements.mSideLayout); mElements.menuspace = mElements.screenwidth * (100 - mElements.mConfig.setSidebarpercent()) / 100; }
重点二:计算组件大小和摆放位置的代码:
/** 控制组件的位置,根据前面设置的id来进行位置摆放*/ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); int measuredWidth = childView.getMeasuredWidth(); int measuredHeight = childView.getMeasuredHeight(); if (mElements.mConfig.setSidebarPosition() % 2 == 0) { if (childView.getId() == 0) { childView.layout( mElements.screenwidth * (mElements.mConfig.setSidebarpercent()) / 100, 0, mElements.screenwidth * (mElements.mConfig.setSidebarpercent()) / 100 + measuredWidth, measuredHeight); } else { childView.layout(0, 0, measuredWidth, measuredHeight); } } if (mElements.mConfig.setSidebarPosition() % 2 == 1) { childView.layout(0, 0, measuredWidth, measuredHeight); } } } /** 度量组件的大小*/ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthSize, heightSize); }
重点三:手势操作事件:
/** 右边滑动时的触摸事件 */ private class mSidebarTouchListenerRight implements OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { int touchX = (int) event.getX(); switch (event.getAction()) { // 按下 case MotionEvent.ACTION_DOWN: mElements.mMotionX = touchX; return true; // 移动 case MotionEvent.ACTION_MOVE: mElements.mdeteX = touchX - mElements.mMotionX; if (mElements.mSideLayout.getScrollX() - mElements.mdeteX + mElements.menuspace >= 0 && mElements.mSideLayout.getScrollX() - mElements.mdeteX <= 0) { mElements.mSideLayout.scrollBy(-mElements.mdeteX, 0); } return true; // 抬起 case MotionEvent.ACTION_UP: if(Math.abs(mElements.mdeteX)<24){ if(mElements.mSideLayout.getScrollX()==mElements.menuspace){ close(); } } if (mElements.mSideLayout.getScrollX() > -mElements.menuspace >> 1) { if(!mScroller.computeScrollOffset()){ mScroller.setFinalY(0); mScroller.setFinalX(0); mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0, mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 300); mElements.mSideLayout.invalidate(); } } else { if(!mScroller.computeScrollOffset()){ mScroller.setFinalY(0); mScroller.setFinalX(-mElements.menuspace); mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0, mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 300); mElements.mSideLayout.invalidate(); } } mElements.mdeteX = 0; mElements.mMotionX = 0; return true; } return false; } } /** 左边触摸时实现的事件 */ private class mSidebarTouchListenerLeft implements OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { int touchX = (int) event.getX(); switch (event.getAction()) { // 按下 case MotionEvent.ACTION_DOWN: mElements.mMotionX = touchX; return true; // 滑动 case MotionEvent.ACTION_MOVE: if (mElements.mMotionX == 0) { mElements.mMotionX = touchX; } mElements.mdeteX = touchX - mElements.mMotionX; if (mElements.mSideLayout.getScrollX() - mElements.menuspace - mElements.mdeteX <= 0 && mElements.mSideLayout.getScrollX() - mElements.mdeteX >= 0) { mElements.mSideLayout.scrollBy(-mElements.mdeteX, 0); } return true; // 抬起 case MotionEvent.ACTION_UP: if(Math.abs(mElements.mdeteX)<24){ if(mElements.mSideLayout.getScrollX()==mElements.menuspace){ close(); } } if (mElements.mSideLayout.getScrollX() >= mElements.menuspace >> 1) { if (!mScroller.computeScrollOffset()) { mScroller.setFinalX(mElements.menuspace); mScroller.setFinalY(0); mScroller.startScroll( mElements.mSideLayout.getScrollX(), 0, mScroller.getFinalX() - mElements.mSideLayout.getScrollX(), 0, 300); mElements.mSideLayout.invalidate(); } } else { if (!mScroller.computeScrollOffset()) { mScroller.setFinalX(0); mScroller.setFinalY(0); mScroller.startScroll( mElements.mSideLayout.getScrollX(), 0, -mElements.mSideLayout.getScrollX(), 0, 300); mElements.mSideLayout.invalidate(); } } mElements.mdeteX = 0; mElements.mMotionX = 0; return true; } return false; } }
留下给主页面和菜单页面添加页面的接口:
/** 给主页添加页面 */ public void addViewtoMenu(View view) { mElements.mMenuLayout.addView(view, new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } /** 给菜单栏添加页面 */ public void addViewtoSide(View view, SideViewinterface sideViewinterface) { SideView mSideView = new SideView(mElements.mContext, sideViewinterface); mSideView.addView(view); mElements.mSideLayout.addView(mSideView, new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); if (mElements.mConfig.setSidebarPosition() % 2 == 0) { mSideView.setOnTouchListener(new mSidebarTouchListenerLeft()); } if (mElements.mConfig.setSidebarPosition() % 2 == 1) { mSideView.setOnTouchListener(new mSidebarTouchListenerRight()); } }
对侧边栏的一些事件的判断:
/** * 判断其是否打开 * @return */ public boolean isopen() { if (mElements.mSideLayout.getScrollX() == 0) { return false; } else { return true; } } /** * 打开侧边栏 */ public void open() { if (!mScroller.computeScrollOffset()) { if(mElements.mConfig.setSidebarPosition()%2==0){ mScroller.setFinalX(mElements.menuspace); mScroller.setFinalY(0); mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0, mElements.menuspace, 0, 600); }else{ mScroller.setFinalX(-mElements.menuspace); mScroller.setFinalY(0); mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0, mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 600); } mElements.mSideLayout.invalidate(); } } /** * 关闭侧边栏 */ public void close() { if (!mScroller.computeScrollOffset()) { if(mElements.mConfig.setSidebarPosition()%2==0){ mScroller.setFinalX(0); mScroller.setFinalY(0); mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0, mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 600); }else{ mScroller.setFinalX(0); mScroller.setFinalY(0); mScroller.startScroll(mElements.mSideLayout.getScrollX(), 0, mScroller.getFinalX()-mElements.mSideLayout.getScrollX(), 0, 600); } mElements.mSideLayout.invalidate(); } }
最后,将出现的SideView代码贴出,主要是为了给应用留出事件接口:
/** * @author 徐晔 */public class SideView extends RelativeLayout{ /** * @author 徐晔 * @note 传递触摸事件的接口 */ public static interface SideViewinterface { /**传递onInterceptTouchEvent*/ public boolean onSideInterceptTouchEvent(MotionEvent ev); } private SideViewinterface mSideViewinterface; public SideView(Context context, AttributeSet attrs, int defStyle,SideViewinterface mSideViewinterface ) { super(context, attrs, defStyle); this.mSideViewinterface=mSideViewinterface; } public SideView(Context context, AttributeSet attrs,SideViewinterface mSideViewinterface ) { super(context, attrs); this.mSideViewinterface=mSideViewinterface; } public SideView(Context context,SideViewinterface mSideViewinterface ) { super(context); this.mSideViewinterface=mSideViewinterface; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mSideViewinterface.onSideInterceptTouchEvent(ev); } /** 测量位置 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); int measuredWidth = childView.getMeasuredWidth(); int measuredHeight = childView.getMeasuredHeight(); childView.layout(0, 0, measuredWidth, measuredHeight); } } /** 度量大小 */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthSize, heightSize); }}
现在代码贴完了;大家只要注意这几个类:
1:Scroller对象,实现平移动画的类
2:onMeasure,和onLayout方法,控制页面大小和进行排版布局的控制;
3:对事件传递控制
以上就是实现自定义View的根本,其实任何自定义View都可以如此实现。大家可以先从需求分析,然后在进行具体的代码书写。今天有侧边栏,但是如果是别的页面呢?其实原理是一样的。如何摆放,对手势的操作反馈,事件传递的控制,动画的实现;将问题分解为一块一块的,那样解决问题就游刃有余。
- Android自定义View之侧边栏初探
- android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
- android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
- android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
- 【自定义View】2.Android滑动侧边栏SlidingMenu的原理
- Android初探自定义View
- android自定义view初探
- 自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
- Android 仿QQ侧边栏,自定义view的学习 <Garry进阶(三)>
- Android界面动画初探之--探索侧边栏折叠效果的实现
- 自定义View实现侧边索引
- 自定义View,实现侧边索引
- Android自定义View之一:初探实例
- Android自定义View之一:初探实例
- Android自定义View之一:初探实例
- Android自定义View之一:初探实例
- Android自定义View之一:初探实例
- Android自定义View之一:初探实例
- Java关键字final、static使用总结
- 矩阵快速幂
- 网络请求
- 在ios7中获取唯一标识符(UDID/UUID)
- Android Studio 获取数字签名的方法
- Android自定义View之侧边栏初探
- 无法解析的外部符号 __imp__timeGetTime@0
- 接口到底能不能实例化。
- Eclipse中安装Birt插件及基础教程
- Android-拦截短信(BroadcastReceiver)
- i节点,容易被人遗忘的节点
- 初学PHPExcel
- RMQ模板
- 复杂表单的动态生成与动态验证