1、 分析:一个容器(SlidingMenu)中装了两个内容(菜单和主界面)
2、 创建一个类继承于ViewGroup:class SlidingMenu extends ViewGroup
3、 布局实现:
<com.itheima.slidingmenu.SidingMenu
android:id="@+id/sidingMenu"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/menu"/>
<include layout="@layout/main"/>
</com.itheima.slidingmenu.SidingMenu>
注意事项:TextView 要想响应点击事件,需要设置 clickable 为 true
4、 测量控件大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);// 测量SlidingMenu
menu = getChildAt(0); // 获取菜单
main = getChildAt(1); // 获取主界面
menuWidth = menu.getLayoutParams().width; // 获取菜单宽
menu.measure(menuWidth, heightMeasureSpec); // 测量菜单
main.measure(widthMeasureSpec, heightMeasureSpec); // 测量主界面
}
5、 排版容器中的子View
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 对子View进行排版,子View的0,0坐标是SlidingMenu的左上角。
// 对菜单进行排版
int menuLeft = -menuViewWidth; // 菜单的left坐标在负的菜单宽的位置
int menuTop = 0; // 菜单的top坐标在0的位置
int menuRight = 0; // 菜单的right坐标在0的位置
int menuBottom = b - t; // 菜单的bottom坐标在容器的最底边
menuView.layout(menuLeft, menuTop, menuRight, menuBottom);
// 对主界面进行排版
int mainLeft = 0; // 主界面的left坐标在0的位置
int mainTop = 0; // 主界面的top坐标在0的位置
int mainRight = r - l; // 主界面的right坐标在容器的最右边
int mainBottom = b - t; // 主界面的bottom坐标在容器的最底边
mainView.layout(mainLeft, mainTop, mainRight, mainBottom);
}
6、 滚动界面
对一个View执行动画时,是以View的内容的最左上作为0,0点坐标的,如下:
scrollBy(x,y)在原来的基础进行滑动
scrollTo(x, y)滑到绝对位置,x传正数它是往左边移,传负数往右边移,为了让我们更好理解代码,使其传正数往右移,增加如下方法:
public void scrollTo(int x) {
super.scrollTo(-x, 0);
}
public int getMyScrollX() {
return -super.getScrollX();
}
界面滑动
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
int fingerMoveDistanceX = (int)event.getX() - downX; // 手指移动距离
int destX = fingerMoveDistanceX; // 目标位置
scrollTo(destX); // 滚动到目标位置
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
7、 当滑动界面并抬起手指,然后再次滑动的时候应该在原来位置的基础上再滑动:
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
hasMove = true;
int fingerMoveDistanceX = (int)event.getX() - downX; // 手指移动距离
int destX = currentX + fingerMoveDistanceX; // 目标位置 = 当前位置 + 手指移动距离
scrollTo(destX); // 滚动到目标位置
break;
case MotionEvent.ACTION_UP:
if (!hasMove) {
// 如果没有移动过,则不处理
return true;
}
hasMove = false;
currentX = getMyScrollX();
break;
}
return true;
}
8、 滑动的时候预防超出范围,红色为新增代码
case MotionEvent.ACTION_MOVE:
hasMove = true;
int fingerMoveDistanceX = (int)event.getX() - downX; // 手指移动距离
int destX = currentX + fingerMoveDistanceX; // 目标位置 = 当前位置 + 手指移动距离
// 预防超出范围
if (destX < 0) {
destX = 0;
} else if (destX > menuWidth) {
destX = menuWidth;
}
scrollTo(destX); // 滚动到目标位置
break;
9、 手指松开的时候自动归位,红色为有修改的代码:
case MotionEvent.ACTION_UP:
if (!hasMove) {
// 如果没有移动过,则不处理
return true;
}
hasMove = false;
// 当手松开时,如果滑动距离小于菜单宽/2,说明菜单在屏幕之外比较多,则把菜单全部隐藏,否则全部显示出来
if (getMyScrollX() < menuWidth / 2) {
scrollTo(0);
currentX = 0;
} else {
scrollTo(menuWidth);
currentX = menuWidth;
}
break;
}
10、 实现自动归位的时候是慢慢的滑动,通过Scroller。红色为有修改的代码:
case MotionEvent.ACTION_UP:
if (!hasMove) {
// 如果没有移动过,则不处理
return true;
}
hasMove = false;
// 当手松开时,如果滑动距离小于菜单宽/2,说明菜单在屏幕之外比较多,则菜单全部隐藏,否则全部显示出来
if (getMyScrollX() < menuWidth / 2) {
startScroll(0); //滑到 0
} else {
startScroll(menuWidth);//滑到菜单宽
}
break;
}
/**
* 开始慢慢移动
* @param startX 从哪里开始移动
* @param destX 移动到哪里
*/
private void startScroll(int startX, int destX) {
currentX = destX;
int startX = getMyScrollX(); // 开始移动的位置
int distanceX = (int) (destX - startX); // x方向移动的距离是多少
int duration = 1000; // 移动需要的时间
scroller.startScroll(startX, 0, distanceX, 0, duration);
invalidate();
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) { // 数据没有模拟完的话会返回true
scrollTo(scroller.getCurrX());
invalidate();
}
}
11、 实现自动归位的时候,按移动的距离多少来决定移动的时间需要多少
private void startScroll(, int destX) {
currentX = destX;
int startX = getMyScrollX(); // 开始移动的位置
int distanceX = (int) (destX - startX); // 移动的距离
int maxDistanceX = menuWidth; // 移动的最大距离
float maxDuration = 1000f; // 移动最大距离需要的时候
float scale = maxDuration / maxDistanceX; // 算出最大时间与最大距离的比例
int duration = Math.abs((int) (distanceX * scale)); // 移动所需要的时间
scroller.startScroll(startX, 0, distanceX, 0, duration);
invalidate();
}
12、 实现点击按钮控制菜单的显示和隐藏
/** 菜单显示开关,要么开,要么关 */
public void toggle() {
if (getMyScrollX() > 0) {
// 如果有滑动,说明菜单是显示出来的,需要隐藏
startScroll(0);
} else {
startScroll(menuWidth);
}
}
解决在ScrollView上左右滑动时无法滑动菜单问题:通过拦截Touch事件
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int) ev.getX();
downY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float distanceX = ev.getX() - downX;
float distanceY = ev.getY() - downY;
if (Math.abs(distanceX) > Math.abs(distanceY)) {
// 如果x移动距离大于y移动距离,则认为是水平移动,拦截事件不让子类使用touch事件
return true; // true代表拦截touch事件,则子View就接收不到touch事件了
}
break;
}
return super.onInterceptTouchEvent(ev);
}