前面写了几篇关于进程/线程原理的;感觉和应用层关系不大;但是实际上,写代码和做事情一样,都注重的是一个严谨的思维习惯;如果你真的深刻的理解了底层进程/线程交互模型,内存管理等;那么对你做APP也是有很大帮助的;比如为进程分配资源的时候,系统的一套思路就很值得借鉴;就能够避免应用中的内存问题;而管理多线程的思路更是值得在开发中借鉴。好了废话不多说;现在回到本章的主题,Android中从侧边栏的实现看自定义View;
首先我们知道,Android给应用层提供的组件有ViewFroup和VIew;其中ViewGroup是继承于View的;View相当于一个页面,但是ViewGroup可以容纳很多View;
现在分析下侧边栏的实现,侧边栏有2个页面,一个是菜单栏,一个是展示内容的页面;这两个页面都容纳在一个ViewGroup中;因此实际上侧边栏是由三个ViewGroup组成,背景是个ViewGroup,容纳了两个子ViewGroup,一个装载菜单,一个装载内容页面;
侧边栏有手势判断事件,这个可能是里面的难点;关于事件传递机制,可以参考
http://blog.youkuaiyun.com/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都可以如此实现。大家可以先从需求分析,然后在进行具体的代码书写。今天有侧边栏,但是如果是别的页面呢?其实原理是一样的。如何摆放,对手势的操作反馈,事件传递的控制,动画的实现;将问题分解为一块一块的,那样解决问题就游刃有余。