手机版qq有一个挺不错的效果是侧拉菜单,在主界面向右滑动就会在左侧出现关于自己qq的一些信息;
实现侧滑效果目前知道有三种:
一是SlidingDrawer,谷歌在Android4.2之后已经不建议使用
二是DrawerLayout,谷歌提供的挺好用的控件,只需在布局文件中加载即可
三是第三方提供的开源控件,更加的灵活;下面就来简单介绍一下原理
一、需求
1.定义两个布局,菜单和主内容,然后整合在一个自定义的控件中
2.分别测量出菜单和主内容的宽和高
3.主内容全屏显示在屏幕上,那么左侧的菜单就会看不到,达到了隐藏菜单的效果
4.处理屏幕的onTouchEvent()事件,实现左右滑动控制菜单的隐藏与显示
二、具体实现
1.定义布局文件菜单和主内容,然后整合在自定义的SlideMenu控件中
2.在SlideMenu中通过onMeasure测量布局文件的宽和高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//测量菜单的宽和高,宽为240dip,高为父窗体高度
//0代表菜单
View menuView = getChildAt(0);
menuView.measure(menuView.getLayoutParams().width, heightMeasureSpec);
//测量内容正文的宽和高,与父窗体一致
//1代表正文内容
View contentView = getChildAt(1);
contentView.measure(widthMeasureSpec, heightMeasureSpec);
}
在系统底层measure调用了onMeasure(定义了宽高的约束)
onMeasrue调用了setMeasureDimension(真实的宽高信息)
3.通过onLayout方法绘制布局
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//把测量出菜单和内容绘制屏幕上
View menuView = getChildAt(0);
//设置菜单在屏幕的左侧显示(即隐藏)
menuView.layout(-menuView.getMeasuredWidth(), t, 0, b);
//主内容的绘制
View contentView = getChildAt(1);
contentView.layout(l, t, r, b);
}
4.处理屏幕的onTouchEvent事件实现菜单的隐藏与显示
@Override
public boolean onTouchEvent(MotionEvent event) {
int scrollX;
switch (event.getAction()) {
//按下
case MotionEvent.ACTION_DOWN:
downX = (int) event.getX();
break;
//移动
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getX();
//偏移量
int deltax = downX - moveX;
scrollX = getScrollX() + deltax;
//根据当前位置确定屏幕的显示
if (scrollX < -getChildAt(0).getMeasuredWidth()) {
scrollTo(-getChildAt(0).getMeasuredWidth(), 0);
} else if (scrollX > 0) {
scrollTo(0, 0);
} else {
scrollBy(deltax, 0);
}
downX = moveX;
break;
//抬起
case MotionEvent.ACTION_UP:
int center = -getChildAt(0).getMeasuredWidth() / 2;
scrollX = getScrollX();
if (scrollX < center) {
currentState = SCREEN_CONTENT;
} else {
currentState = SCREEN_MAIN;
}
switchScreen();
break;
}
return true;
}
/**
* 根据currentState的状态来更改屏幕的显示
*/
private void switchScreen() {
int startX = getScrollX(); //当前屏幕的X位置
int dX = 0; //增量=目的位置-当前位置
switch (currentState) {
case SCREEN_CONTENT:
// scrollTo(-getChildAt(0).getMeasuredWidth(),0);
dX = -getChildAt(0).getMeasuredWidth() - startX;
break;
case SCREEN_MAIN:
// scrollTo(0,0);
dX = 0 - startX;
break;
}
int duration = Math.abs(dX) * 10;
if (duration > 1000) {
duration = 1000;
}
scroller.startScroll(startX, 0, dX, 0, duration);
invalidate();
}
@Override
public void computeScroll() {
//当不在模拟数据时候跳出递归
if (scroller.computeScrollOffset()) {
//获取当前模拟的数据
int currX = scroller.getCurrX();
//更新位置
scrollTo(currX, 0);
invalidate();
}
}
滑动的分析:
1.当点击屏幕时记录下当前的位置downX,在移动的过程中根据当前位置moveX和downX计算偏移量
根据偏移量的值不断的修正菜单和主内容在屏幕的显示位置;
2.当抬起时,如果菜单显示出来的内容是否大于二分之一,那么滑动显示菜单,反之显示主内容
3.currentState用来记录当前屏幕显示的是菜单还是主内容,并且是屏幕固定时偏移量计算的依据
4.scroller.startScroll(startX,0,dX,0,duration)
用来模拟X轴从startX到偏移dX经过duration时间的过程,但是这个方法只是模拟,并不会改变
屏幕的显示,还需要通过invalidate()来修改屏幕的显示
5.invalidate()在ViewGroup中通过调用DrawChild()->再调view.draw->computeScroll修改屏幕
显示的内容,由于startScroll的数据是缓慢变化的,因此需要用递归不断的更新当前的显示,就
实现了松手后菜单缓慢移动的效果
三、回调简介
Android回调方法用的特别多,其原理类似于设计模式中的观察者模式,回调通过自身提供一个内部
接口,在接口定义统一的方法,那么不管是谁调用我,只要实现我接口中定义的方法,使代码有了很
好的封装性
四、个人总结
写自定义控件一定要有的步骤:
1.分析控件的实现都有什么状态
2.找出状态之间转换的临界条件
3.实现即可