自定义控件–侧滑菜单栏
这里用到的知识-事件分发机制:
dispatchTouchEvent
> 用于控制事件是否往下下发
> 分发事件
* super : 事件会接着往下走
* true | false: 事件不会往下下发了,到此结束。
* onInterceptTouchEvent
> 用于控制事件是否会传递给孩子
> 拦截事件
* super | false: 不拦截, 事件会传递给孩子
* true : 拦截事件,不会传递给孩子,自己处理。
* onTouchEvent
> 事件是否处理,如果不处理,会交给父容器去处理
> 处理事件
* super | false : 事件还会传给父容器
* true : 事件我们自己处理,不要给别人了。
> dispatchTouchEvent -- onInterceptTouchEvent -- onTouchEvent
布局部分
activity_main.xml–
<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.lxk.slidemenu.view.SlideMenuView android:id="@+id/sv" android:layout_width="wrap_content" android:layout_height="wrap_content"> <include layout="@layout/view_content"/> <include layout="@layout/view_slidemenu"/> </com.lxk.slidemenu.view.SlideMenuView> </RelativeLayout>
view_content.xml内容菜单
<?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:background="@drawable/top_bar_bg" android:layout_width="match_parent" android:layout_height="wrap_content" <ImageView android:id="@+id/iv_sm" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/main_back" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/top_bar_divider" /> <TextView android:layout_gravity="center_vertical" android:textColor="#FFFFFF" android:textSize="20sp" android:text="新闻中心" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="内容区域" android:textSize="20sp" /> </RelativeLayout> </LinearLayout>
view_slidemenu.xml 测滑菜单内容
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="match_parent"
android:orientation="vertical" >
<ScrollView
android:background="@drawable/menu_bg"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:orientation="vertical"
android:padding="15dp"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
style="@style/Tar_Style"
android:drawableLeft="@drawable/tab_news"
android:text="新闻" />
<TextView
style="@style/Tar_Style"
android:drawableLeft="@drawable/tab_read"
android:text="订阅" />
<TextView
style="@style/Tar_Style"
android:drawableLeft="@drawable/tab_local"
android:text="本地" />
<TextView
style="@style/Tar_Style"
android:drawableLeft="@drawable/tab_ties"
android:text="跟帖" />
<TextView
style="@style/Tar_Style"
android:drawableLeft="@drawable/tab_pics"
android:text="图片" />
<TextView
style="@style/Tar_Style"
android:drawableLeft="@drawable/tab_ugc"
android:text="话题" />
<TextView
style="@style/Tar_Style"
android:drawableLeft="@drawable/tab_vote"
android:text="投票" />
<TextView
style="@style/Tar_Style"
android:text="聚合读物" />
</LinearLayout>
</ScrollView>
</LinearLayout>
代码部分
MainActivity
package com.lxk.slidemenu;
import com.lxk.slidemenu.view.SlideMenuView;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
public class MainActivity extends Activity implements OnClickListener {
private SlideMenuView mSv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
//初始化控件
private void initView() {
ImageView sm=(ImageView) findViewById(R.id.iv_sm);
mSv = (SlideMenuView) findViewById(R.id.sv);
sm.setOnClickListener(this);
}
@Override
public void onClick(View v) {
//打开或关闭侧栏菜单
mSv.setShow();
}
}
SlideMenuView
package com.lxk.slidemenu.view;
import android.R.id;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
/**
* 自定义绘画三步骤
* 1.测量--测量控件的大小
* 2.布局--设置控件的位置
* 3.绘画--
*
*/
public class SlideMenuView extends ViewGroup{
private static boolean isShow = false;
private Scroller mScroller;
private View mContentView;
private View mSlideView;
public SlideMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
//负责处理
mScroller = new Scroller(context);
}
//测量控件的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//设置孩子的控件的大小--这里使用自己在布局的大小的
measureChildren(widthMeasureSpec, heightMeasureSpec);
//设置自己的控件的大小
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//设置控件的位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//设置孩子的位置这里一共有两个孩子--一个是contentview,一个是slidemenuview
mContentView = getChildAt(0);//内容菜单
mSlideView = getChildAt(1);//测滑菜单
//设置内容菜单的位置(一开始显示的内容菜单)--一个View的位置一般由它的坐上角坐标(left,top)和它的右下角(right,bottom)坐标决定的
//
int contentLeft=0; //左上角的X坐标
int contentTop=0; //左上角的Y坐标
int contentRight=mContentView.getMeasuredWidth(); //右下角的X坐标--getMeasuredWidth()方法是获取该控件的长度,getWidth()是布局设置完成后可以设置了
int contentBotoom=mContentView.getMeasuredHeight();//右下角的Y坐标
mContentView.layout(contentLeft, contentTop, contentRight, contentBotoom);
//设置测滑菜单的位置
int slideLeft=-mSlideView.getMeasuredWidth(); //左上角的X坐标
int slideTop=0; //左上角的Y坐标
int slideRight=0; //右下角的X坐标--getMeasuredWidth()方法是获取该控件的长度,getWidth()是布局设置完成后可以设置了
int slideBotoom=mSlideView.getMeasuredHeight();//右下角的Y坐标
mSlideView.layout(slideLeft, slideTop, slideRight, slideBotoom);
}
//绘画--这里不需要绘画
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
//创建按下的坐标,移动的坐标,抬起的坐标
float mDwonX,mMoveX,mUpX;
//因为是要触摸滑动所要复写View控件的一个方法
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//鼠标按下的时候的X坐标---因为滑动是针对左右移动所以不考虑Y坐标
mDwonX=event.getX();
//消除之前没完成的动作防止下一次按的时候之前动作还么完成
mScroller.forceFinished(true);
break;
case MotionEvent.ACTION_MOVE:
//鼠标移到到某个位置的时候的X坐标
mMoveX=event.getX();
//处理移动的事件处理
performMove();
break;
case MotionEvent.ACTION_UP:
mUpX=event.getX();//抬起时的X的坐标
//鼠标抬起的时候处理事件
performUp();
break;
default:
break;
}
return true;//这里是返回true相当是只要发生触摸--事件都由我们自己处理不需要别人false是交给系统处理
}
//鼠标抬起时响应的事件
private void performUp() {
int minddle=mSlideView.getMeasuredWidth()/2;
//鼠标抬起时如果向右滑动距离小于菜单栏的一般就直接退回原点
if (-getScrollX()<minddle) {
System.out.println("少一一半= "+getScrollX());
closeMenu();//关闭测滑菜单
} else { //当向右滑动大于的时候就直接拖到右边边界点
openMenu();//打开侧栏菜单
}
}
public void openMenu() {
int dx=mSlideView.getWidth()+getScrollX();//移动剩下的距离刚好把侧栏菜单一出来
//设置移动需要的时间--也可以直接具体数值但是因为为了达到效果所以这里就动态的给出
int duration=dx*8;
if (duration>1000) {//如果duration大于一秒则就拿一秒来算,时间太长了不行
duration=1000;
}
/*
* Scroller类是移动动画效果;参数一:是开始移动的X坐标;参数二:开始移动的Y坐标
* 参数三:水平移动距离,参数四:垂直移动的距离,参数五:总共持续的时间
*/
mScroller.startScroll(getScrollX(), 0,-dx, 0, duration);
//注意需要重新绘制一下
invalidate();
System.out.println("超一一半= "+getScrollX());
}
//关闭菜单
public void closeMenu() {
int startX=getScrollX();//从当前的距离做为X坐标
int startY=0;
int dx=-getScrollX();//向右移动了多少距离,向相反向左移动多少距离回去,负数表示向右移动,正数表示想做移动
int dy=0;//垂直方向不移动
//设置移动需要的时间--也可以直接具体数值但是因为为了达到效果所以这里就动态的给出
int duration=dx*8;
if (duration>1000) {//如果duration大于一秒则就拿一秒来算,时间太长了不行
duration=1000;
}
/*
* Scroller类是移动动画效果;参数一:是开始移动的X坐标;参数二:开始移动的Y坐标
* 参数三:水平移动距离,参数四:垂直移动的距离,参数五:总共持续的时间
*/
mScroller.startScroll(startX, startY, dx, dy, duration);
//注意需要重新绘制一下
invalidate();
}
//这个是配置mScroller来完成的事件
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {//如果当前的每移动完则继续移动完
int currX = mScroller.getCurrX();
scrollTo(currX, 0);
//注意需要重新绘制一下
invalidate();
}
}
//处理移动的事件
private void performMove() {
/*
* scrollTo(x,y)--x为负数就是把左边不可见的x坐标内容拖出来显示,x为正数的时候,
* 把右边不可见的x左边的内容拖出来显示
*/
int dx=Math.round(mDwonX-mMoveX);//计算移动的距离
/*
* 首先判断一下左边的边界的问题--当鼠标往右边滑动的时候,左边拖出来的最大的长度应该是测滑菜单栏的长度.
*/
//由于鼠标只要移动一点距离而且为正数,无法判断范围
int finalX=dx+getScrollX();//
if (finalX<-mSlideView.getMeasuredWidth()) {//当往右边的移动,计算dx为负数,getScrollX()为负数相加得负数
dx=-mSlideView.getMeasuredWidth();
scrollTo(dx, 0);
} else if(finalX>0){//往左边移动的时候,最多把测滑菜单栏刚好全部推到出去的时候移动的距离
dx=0;
scrollTo(dx, 0);
}else{//如果是在中间的范围则使用srollBy(x,y);来滑动,这个方法是根据当前的坐标进行叠加移动,所以每移动一次后就要把当前的坐标赋值给作为按下的坐标
scrollBy(dx, 0);
mDwonX=mMoveX;
}
}
//对外暴露一个方法,给MianActivity来点击按钮来调用显示还是隐藏测滑菜单
public void setShow(){
if (isShow) {//如果现在正开则隐藏测滑菜单栏
isShow=false;
closeMenu();
} else {//如果现在正开则显示测滑菜单栏
isShow=true;
openMenu();
}
}
/*
* 事件的传递机制三步骤:dispatchTouchEvent用于控件是否往下传递,2,onInterceptTouchEvent:用于控制事件是否会传递给孩子,3、onTouchEvent:事件是否处理,如果不处理,会交给父容器去处理
*/
//dispatchTouchEvent用于控件是否往下分发:分发事件:super接着往下走,true|false事件不会往下发生到此结束
//用于控制事件是否会传递给孩子
//拦截:* super | false: 不拦截, 事件会传递给孩子
// * true : 拦截事件,不会传递给孩子,自己处理。
float mDownY,mMoveY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//在测滑菜单中如果是水平滑动我们需要移动拦截,如果是垂直移动事件往下发
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDwonX=ev.getX();
mDownY=ev.getY();
break;
case MotionEvent.ACTION_MOVE:
mMoveX=ev.getX();
mMoveY=ev.getY();
//如果水平移动的距离大于垂直的则返回true
int dx=Math.round(mMoveX-mDwonX);
int dy=Math.round(mMoveY-mDownY);
if (dx>dy) {
System.out.println("水平移动--");
return true;
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
}