Android 自定义ViewGroup(侧滑菜单)
1,实现效果
2,实现逻辑
【1】界面搭建,menu菜单 提取样式
-
main 布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/top_bar_bg"
android:orientation="horizontal" >
<Button
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/main_back" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="3dp"
android:src="@drawable/top_bar_divider" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:text="小帅xxx"
android:textColor="#ffffff"
android:textSize="25sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="牛奶奶说牛奶是刘奶奶的奶奶····"
android:gravity="center"
android:textSize="25sp" />
</LinearLayout>
-
抽取字体的样式
<style name="MenuText" parent="android:Widget.TextView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:drawablePadding">15dp</item>
<item name="android:gravity">center_vertical</item>
<item name="android:padding">15dp</item>
<item name="android:textColor">#fff</item>
<item name="android:textSize">25sp</item>
<item name="android:textStyle">bold</item>
</style>
-
menu 的布局
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="240dp"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="240dp"
android:layout_height="match_parent"
android:background="@drawable/menu_bg"
android:orientation="vertical" >
<TextView
style="@style/MenuText"
android:background="#571F2C"
android:drawableLeft="@drawable/tab_news"
android:text="新闻" />
<TextView
style="@style/MenuText"
android:drawableLeft="@drawable/tab_read"
android:text="订阅" />
<TextView
style="@style/MenuText"
android:drawableLeft="@drawable/tab_ties"
android:text="跟帖" />
<TextView
style="@style/MenuText"
android:drawableLeft="@drawable/tab_pics"
android:text="图片" />
<TextView
style="@style/MenuText"
android:drawableLeft="@drawable/tab_ugc"
android:text="话题" />
<TextView
style="@style/MenuText"
android:drawableLeft="@drawable/tab_vote"
android:text="投票" />
<TextView
style="@style/MenuText"
android:drawableLeft="@drawable/tab_focus"
android:text="聚合阅读" />
</LinearLayout>
</ScrollView>
【2】定义一个类创建自定义控件
public class SlidingMenu extends RelativeLayout{
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}
//在这个方法里面 自己对孩子进行排版 不使用系统默认的排版方式
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
-
布局中声明控件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.xiaoshuai.slidingmenuhm.SlidingMenu
android:id="@+id/slidingMenu1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 添加自己的孩子 menu菜单和main主界面 -->
<include layout="@layout/menu" />
<include layout="@layout/main" />
</com.xiaoshuai.slidingmenuhm.SlidingMenu>
</RelativeLayout>
MainActivity在加载布局中去掉标题栏
//去掉标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
-
SlidingMenu的onlayout 自己进行排版
//在这个方法里面 自己对孩子进行排版 不使用系统默认的排版方式
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//[1]找到menu和main孩子
View menuView = getChildAt(0);
View mainView = getChildAt(1);
//[2]获取菜单的宽度
menuWidth = menuView.getMeasuredWidth();
//[3]对menu菜单进行排版
menuView.layout(-menuWidth, t, l, b);
//[4]对main界面排版
mainView.layout(l, t, r, b);
}
【3】移动效果:
-
创建onTouchEvent获取对应的3种事件,按下和移动获取坐标。获取移动的偏移量
//处理当前view的事件
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: //按下
//[1]获取手指按下的坐标
downX = event.getX();
break;
case MotionEvent.ACTION_MOVE: //移动
//[2]算出移动的距离
float moveX = event.getX();
distaceX = (int) (moveX - downX)+currentMenuPosition;
//[4]开始滚动view
startScrollViewContent(distaceX);
break;
重写scrollTo方法来实现移动
/**
* 由于scrollTo 方法 系统在实现的时候 传入正值往左移动 传入负值往右移动 所以我重写这个方
* 符合中国 人的思维
* @param x
*/
public void startScrollViewContent(int x){
super.scrollTo(-x, 0);
}
-
对边界进行处理 menuWidth 是menu 宽度
case MotionEvent.ACTION_MOVE: //移动
//[2]算出移动的距离
float moveX = event.getX();
distaceX = (int) (moveX - downX)+currentMenuPosition;
//[3]对边界进行处理
if (distaceX <= 0) {
distaceX = 0;
}else if (distaceX >=menuWidth) {
distaceX = menuWidth;
}
//[4]开始滚动view
startScrollViewContent(distaceX);
break;
-
抬起时做距离判断效果
case MotionEvent.ACTION_UP: //抬起
//[5]当手指抬起后, 如果移动的距离 < 菜单的宽/2 就回到左边 否则像右移动
if (distaceX < menuWidth/2) {
startScrollViewContent(0);
}else {
startScrollViewContent(menuWidth);
}
break;
}
【4】实现平滑滚动
invalidate--drawchild ---child.draw --computeScroll()
-
创建对象
//创建Scroller的实例 可以收集数据 并且产生一个动画效果
mScroller = new Scroller(getContext());
-
ACTION_UP方法中实现平滑滚动效果
-
currentMenuPosition 定义的一个菜单移动到哪里的变量 case MotionEvent.ACTION_UP: //抬起 //[5]当手指抬起后, 如果移动的距离 < 菜单的宽/2 就回到左边 否则像右移动 if (distaceX < menuWidth/2) { currentMenuPosition = 0; }else { currentMenuPosition = menuWidth; } //[6]当一段逻辑同时用到多次 我们最好做抽取 int startX = distaceX; //就是移动的距离 int endX = currentMenuPosition; int dx = endX - startX; int duration = Math.abs(dx)*20; //产生模拟的数据 mScroller.startScroll(startX, 0, dx, 0, duration); //调用invalidata 请求绘制 invalidate(); //---->drawchild ---child.draw --computeScroll() break;
-
computeScroll 方法中获取模拟的移动数据进行滑动
//取出我们模拟滚动的数据
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
//[1]取出我们模拟的数据
int currX = mScroller.getCurrX();
//[2]调用startScrollViewContent 让view的内容滚动
startScrollViewContent(currX);
invalidate();
}
}
【5】在menu布局中无法左右滑动,处理孩子的滑动事件
-
onInterceptTouchEvent判断孩子滑动的距离
//当用户横着滑动的距离 > 竖着滑动的距离 就拦截事件 不让孩子处理事件 否则就让孩子处理事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getX();
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float moveX = ev.getX();
float moveY = ev.getY();
//[1]算出X轴移动的距离 和 Y移动的距离
int distanceX = (int) (moveX - downX);
int distanceY = (int) (moveY - downY);
//[2]如果x轴移动的距离大于Y轴移动的距离 就拦截事件
if (Math.abs(distanceX) > Math.abs(distanceY)) {
return true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}
【6】MainActivity 中点击事件实现平滑滚动
-
设置点击事件
//[1]找到slidingmenu
sm = (SlidingMenu) findViewById(R.id.slidingMenu1);
//[2]给主页面的按钮设置点击事件 当菜单是关闭状态 点击按钮菜单打开 如果是打开状态就关闭
findViewById(R.id.btn_back).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
sm.setOnMenuIsOpen();
}
});
创建setOnMenuIsOpen 方法,判断菜单的位置来确认开或者关
//通过这个方法控制菜单打开或者关闭
public void setOnMenuIsOpen() {
int startX = 0;
if (currentMenuPosition == 0) {
//说明菜单是关闭状态 需要打开
currentMenuPosition = menuWidth;
}else if (currentMenuPosition == menuWidth) {
//说明菜单是打开状态 需要关闭
currentMenuPosition = 0;
startX = menuWidth;
}
//调用平滑滚动的方法
startScroller(startX, currentMenuPosition);
}
3,实现的全部代码
package com.xiaoshuai.www.slidingmenu;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.Scroller;
public class SlidingMenu extends RelativeLayout{
private float downX;
/**代表菜单的宽度**/
private int menuWidth;
private int distaceX;
private Scroller mScroller;
/**代表当前菜单的位置**/
private int currentMenuPosition;
private float downY;
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
//创建Scroller的实例 可以收集数据 并且产生一个动画效果
mScroller = new Scroller(getContext());
}
//在这个方法里面 自己对孩子进行排版 不使用系统默认的排版方式
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//[1]找到menu和main孩子
View menuView = getChildAt(0);
View mainView = getChildAt(1);
//[2]获取菜单的宽度
menuWidth = menuView.getMeasuredWidth();
//[3]对menu菜单进行排版
menuView.layout(-menuWidth, t, l, b);
//[4]对main界面排版
mainView.layout(l, t, r, b);
}
//当用户横着滑动的距离 > 竖着滑动的距离 就拦截事件 不让孩子处理事件 否则就让孩子处理事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getX();
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float moveX = ev.getX();
float moveY = ev.getY();
//[1]算出X轴移动的距离 和 Y移动的距离
int distanceX = (int) (moveX - downX);
int distanceY = (int) (moveY - downY);
//[2]如果x轴移动的距离大于Y轴移动的距离 就拦截事件
if (Math.abs(distanceX) > Math.abs(distanceY)) {
return true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}
//处理当前view的事件
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: //按下
//[1]获取手指按下的坐标
downX = event.getX();
break;
case MotionEvent.ACTION_MOVE: //移动
//[2]算出移动的距离
float moveX = event.getX();
distaceX = (int) (moveX - downX)+currentMenuPosition;
//[3]对边界进行处理
if (distaceX <= 0) {
distaceX = 0;
}else if (distaceX >=menuWidth) {
distaceX = menuWidth;
}
//[4]开始滚动view
startScrollViewContent(distaceX);
break;
case MotionEvent.ACTION_UP: //抬起
//[5]当手指抬起后, 如果移动的距离 < 菜单的宽/2 就回到左边 否则像右移动
if (distaceX < menuWidth/2) {
currentMenuPosition = 0;
}else {
currentMenuPosition = menuWidth;
}
//[6]当一段逻辑同时用到多次 我们最好做抽取
int startX = distaceX; //就是移动的距离
int endX = currentMenuPosition;
//[7]实现平滑滚动
startScroller(startX, endX);
break;
}
return true;//让当前view消费事件
}
//实现平滑滚动
private void startScroller(int startX, int endX) {
int dx = endX - startX;
int duration = Math.abs(dx)*20;
//产生模拟的数据
mScroller.startScroll(startX, 0, dx, 0, duration);
//调用invalidata 请求绘制
invalidate(); //---->drawchild ---child.draw --computeScroll()
}
/**
* 由于scrollTo 方法 系统在实现的时候 传入正值往左移动 传入负值往右移动 所以我重写这个方
* 符合中国 人的思维
* @param x
*/
public void startScrollViewContent(int x){
super.scrollTo(-x, 0);
}
//取出我们模拟滚动的数据
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
//[1]取出我们模拟的数据
int currX = mScroller.getCurrX();
//[2]调用startScrollViewContent 让view的内容滚动
startScrollViewContent(currX);
invalidate();
}
}
//通过这个方法控制菜单打开或者关闭
public void setOnMenuIsOpen() {
int startX = 0;
if (currentMenuPosition == 0) {
//说明菜单是关闭状态 需要打开
currentMenuPosition = menuWidth;
}else if (currentMenuPosition == menuWidth) {
//说明菜单是打开状态 需要关闭
currentMenuPosition = 0;
startX = menuWidth;
}
//调用平滑滚动的方法
startScroller(startX, currentMenuPosition);
}
}