Android自定义ViewGroup打造各种风格的SlidingMenu_Android

上篇给大家介绍QQ5.0侧滑菜单的视频课程,对于侧滑的时的动画效果的实现有了新的认识,似乎打通了任督二脉,目前可以实现任意效果的侧滑菜单了,感谢鸿洋大大!!

用的是HorizontalScrollView来实现的侧滑菜单功能,HorizontalScrollView的好处是为我们解决了滑动功能,处理了滑动冲突问题,让我们使用起来非常方便,但是滑动和冲突处理都是android中的难点,是我们应该掌握的知识点,掌握了这些,我们可以不依赖于系统的API,随心所欲打造我们想要的效果,因此这篇文章我将直接自定义ViewGroup来实现侧滑菜单功能

首先我们先来看一看效果图,第一个效果图是一个最普通的侧滑菜单,我们一会儿会先做出这种侧滑菜单,然后再在此基础上实现另外两个效果

第一种

第二种

第三种

实现第一种侧滑菜单,继承自ViewGroup

继承自ViewGroup需要我们自己来测量,布局,实现滑动的效果,处理滑动冲突,这些都是一些新手无从下手的知识点,希望看了这篇文章后可以对大家有一个帮助

自定义ViewGroup的一般思路是重写onMeasure方法,在onMeasure方法中调用measureChild来测量子View,然后调用setMeasuredDimension来测量自己的大小。然后重写onLayout方法,在onLayout中调用子View的layout方法来确定子View的位置,下面我们先来做好这两件工作

初始时候我们的Content应该是显示在屏幕中的,而Menu应该是显示在屏幕外的。当Menu打开时,应该是这种样子的

 

mMenuRightPadding是Menu距屏幕右侧的一个距离,因为我们Menu打开后,Content还是会留一部分,而不是完全隐藏的

public class MySlidingMenu extends ViewGroup {
public MySlidingMenu(Context context) {
this(context, null, 0);
}
public MySlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MySlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
//获取屏幕的宽和高
mScreenWidth = metrics.widthPixels;
mScreenHeight = metrics.heightPixels;
//设置Menu距离屏幕右侧的距离,convertToDp是将代码中的100转换成100dp
mMenuRightPadding = convertToDp(context,100);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//拿到Menu,Menu是第0个孩子
mMenu = (ViewGroup) getChildAt(0);
//拿到Content,Content是第1个孩子
mContent = (ViewGroup) getChildAt(1);
//设置Menu的宽为屏幕的宽度减去Menu距离屏幕右侧的距离
mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;
//设置Content的宽为屏幕的宽度
mContentWidth = mContent.getLayoutParams().width = mScreenWidth;
//测量Menu
measureChild(mMenu,widthMeasureSpec,heightMeasureSpec);
//测量Content
measureChild(mContent, widthMeasureSpec, heightMeasureSpec);
//测量自己,自己的宽度为Menu宽度加上Content宽度,高度为屏幕高度
setMeasuredDimension(mMenuWidth + mContentWidth, mScreenHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//摆放Menu的位置,根据上面图可以确定上下左右的坐标
mMenu.layout(-mMenuWidth, 0, 0, mScreenHeight);
//摆放Content的位置
mContent.layout(0, 0, mScreenWidth, mScreenHeight);
}

/**
* 将传进来的数转化为dp
*/
private int convertToDp(Context context , int num){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,num,context.getResources().getDisplayMetrics());
}
}

目前我们的侧滑菜单中的两个子View的位置应该是这个样子

 

左侧菜单是隐藏在屏幕左侧外部的,但是现在还不能滑动,如果想要实现滑动功能,我们可以使用View的scrollTo和scrollBy方法,这两个方法的区别是scrollTo是直接将view移动到指定的位置,scrollBy是相对于当前的位置移动一个偏移量,所以我们应该重写onTouchEvent方法,用来计算出当前手指的一个偏移量,然后使用scrollBy方法一点一点的移动,就形成了一个可以跟随手指移动的view的动画效果了

在写代码之前,我们先扫清一下障碍,我们先来弄清楚这些坐标是怎么回事



好了,把这些坐标弄清楚后,我们就简单多了,下面直接看onTouchEvent方法

@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action){
case MotionEvent.ACTION_DOWN:
mLastX = (int) event.getX();
mLastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int currentX = (int) event.getX();
int currentY = (int) event.getY();
//拿到x方向的偏移量
int dx = currentX - mLastX;
if (dx < 0){//向左滑动
//边界控制,如果Menu已经完全显示,再滑动的话
//Menu左侧就会出现白边了,进行边界控制
if (getScrollX() + Math.abs(dx) >= 0) {
//直接移动到(0,0)位置,不会出现白边
scrollTo(0, 0);
} else {//Menu没有完全显示呢
//其实这里dx还是-dx,大家不用刻意去记
//大家可以先使用dx,然后运行一下,发现
//移动的方向是相反的,那么果断这里加个负号就可以了
scrollBy(-dx, 0);
}
}else{//向右滑动
//边界控制,如果Content已经完全显示,再滑动的话
//Content右侧就会出现白边了,进行边界控制
if (getScrollX() - dx <= -mMenuWidth) {
//直接移动到(-mMenuWidth,0)位置,不会出现白边
scrollTo(-mMenuWidth, 0);
} else {//Content没有完全显示呢
//根据手指移动
scrollBy(-dx, 0);
}
}
mLastX = currentX;
mLastY = currentY;
break;
}
return true;
}

现在我们的SlidingMenu依然是不能够水平滑动的,但是listview可以竖直滑动,原因是我们的SlidingMenu默认是不拦截事件的,那么事件会传递给他的子View去执行,也就是说传递给了Content的ListView去执行了,所以listview是可以滑动的,为了简单,我们先重写onInterceptTouchEvent方法,我们返回true,让SlidingMenu拦截事件,我们的SlidingMenu就能够滑动了,但是ListView是不能滑动的,等下我们会进行滑动冲突的处理,现在先实现SlidingMenu的功能

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}

好了,现在我们可以自由的滑动我们的SlidingMenu了,并且进行了很好的边界控制,现在我们再添加个功能,就是当Menu打开大于二分之一时,松开手指,Menu自动打开。当Menu打开小于二分之一时,松开手指,Menu自动关闭。自动滑动的功能我们要借助Scroller来实现

我们在构造方法中初始化一个Scroller

public MySlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
...
mScroller = new Scroller(context);
...
}

然后重写computeScroll方法,这个方法是保证Scroller自动滑动的必须方法,这是一个模板方法,到哪里都这么些就好了

@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}

接着我们在onTouchEvent的ACTION_UP中进行判断,判断当前menu打开了多少

case MotionEvent.ACTION_UP:
if (getScrollX() < -mMenuWidth / 2){//打开Menu
//调用startScroll方法,第一个参数是起始X坐标,第二个参数
//是起始Y坐标,第三个参数是X方向偏移量,第四个参数是Y方向偏移量
mScroller.startScroll(getScrollX(), 0, -mMenuWidth - getScrollX(), 0, 300);
//设置一个已经打开的标识,当实现点击开关自动打开关闭功能时会用到
isOpen = true;
//一定不要忘了调用这个方法重绘,否则没有动画效果
invalidate();
}else{//关闭Menu
//同上
mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 300);
isOpen = false;
invalidate();
}
break;

关于startScroll中的startX和startY好判断,那么dx和dy怎么计算呢?其实也非常简单,比如我们startX坐标为30,我们想移动到-100,那么startX+dx = -100 –> dx = -100 - startX –> dx = -130

好了现在我们就可以实现松开手指后自动滑动的动画效果了
现在我们还需要点击content中左上角的一个三角,如果当前menu没有打开,则自动打开,如果已经打开,则自动关闭的功能,自动滑动的效果我们要借助Scroller.startScroll方法

/**
* 点击开关,开闭Menu,如果当前menu已经打开,则关闭,如果当前menu已经关闭,则打开
*/
public void toggleMenu(){
if (isOpen){
closeMenu();
}else{
openMenu();
}
}
/**
* 关闭menu
*/
private void closeMenu() {
//也是使用startScroll方法,dx和dy的计算方法一样
mScroller.startScroll(getScrollX(),0,-getScrollX(),0,500);
invalidate();
isOpen = false;
}
/**
* 打开menu
*/
private void openMenu() {
mScroller.startScroll(getScrollX(),0,-mMenuWidth-getScrollX(),0,500);
invalidate();
isOpen = true;
}

然后我们可以在MainActivity中拿到我们content左上角三角形的imageview,然后给他设置一个点击事件,调用我们的toggleMenu方法

mMenuToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSlidingMenu.toggleMenu();
}
});

处理滑动冲突

由于我们的menu和content是listview,listview是支持竖直滑动的,而我们的slidingMenu是支持水平滑动的,因此会出现滑动的冲突。刚才我们直接在onInterceptTouchEvent中返回了true,因此SlidingMenu就会拦截所有的事件,而ListView接收不到任何的事件,因此ListView不能滑动了,我们要解决这个滑动冲突很简单,只需要判断当前是水平滑动还是竖直滑动,如果是水平滑动的话则让SlidingMenu拦截事件,如果是竖直滑动的话就不拦截事件,把事件交给子View的ListView去执行

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int) ev.getX() - mLastXIntercept;
int deltaY = (int) ev.getY() - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)){//横向滑动
intercept = true;
}else{//纵向滑动
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
}
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercept;
}

好了,现在我们的滑动冲突就解决了,我们既可以水平滑动SlidingMenu,又可以竖直滑动ListView,那么第一种SlidingMenu就已经实现了,我们再来看看另外两种怎么去实现

实现第二种QQ V6.2.3风格的SlidingMenu

 

这种SlidingMenu是和QQ v6.2.3 的侧滑菜单风格一致的,我们发现Menu和Content的滑动速度是有一个速度差的,实际上我们可以通过修改Menu的偏移量来达到这种效果

 

此时Menu的偏移量为mMenuWidth的2/3,当我们慢慢打开Menu的同时,修改Menu的偏移量,最终修改为0

 

这样就达到了一种速度差的效果,我们只需要在onTouchEvent的ACTION_MOVE和computeScroll中添加一行如下代码就可以

mMenu.setTranslationX(2*(mMenuWidth+getScrollX())/3);

我们分析一下,在最开始,mMenuWidth+getScrollX=mMenuWidth,再乘以2/3,得到的就是mMenuWidth的2/3 , 当我们滑动至Menu完全打开时,mMenuWidth+getScrollX=0 , 这就达到了我们的效果

为什么要在computeScroll中也添加这一行代码呢,因为当我们滑动过程中,如果我们手指离开屏幕,ACTION_MOVE肯定就不执行了,但是当我们手指离开屏幕后,会有一段自动打开或者关闭的动画,那么这段动画应该继续去设置Menu的偏移量,因此我们在computeScroll中也要添加这一行代码。

好了,效果我们已经实现了,只需要去设置Menu的偏移量就可以了,是不是非常简单

实现第三种QQ V5.0风格的SlidingMenu

 

这个效果中Menu有一个偏移的效果,透明度的变化以及放大的效果。Content中有一个缩小的效果。
首先我们要有一个变量,用来记录当前menu已经打开了多少百分比。


这里我们要注意,getScrollX得到的数值正好是负值,所以我们计算的时候要将getScrollX的值取绝对值再去计算,我们在onTouchEvent的MOVE中要计算这个值,同时在computeScroll方法中也要计算这个值,因为当我们手指抬起时,可能会执行一段自动打开或者关闭的动画,那么我们在MOVE中的计算肯定停止了,但是在执行动画的过程中,是Scroller在起作用,那么computeScroll就会执行直到动画结束,因此我们要在computeScroll中同样进行计算

scale = Math.abs((float)getScrollX()) / (float) mMenuWidth;

scale的值是[0,1]的,因此我们就可以根据这个值来对menu的偏移量进行设置。
我们可以通过设置View的setScaleX和setScaleY来对View进行放大缩小,当然这个缩放比例要根据我们的scale值来改变,首先我们的Menu有一个放大的效果,我们就指定为Menu从0.7放大到1.0,那么我们就可以这样写

mMenu.setScaleX(0.7f + 0.3f*scale);
mMenu.setScaleY(0.7f + 0.3f*scale);

透明度是从0到1的,所以我们直接用scale的值就可以了

mMenu.setAlpha(scale);

我还给Menu设置了一个偏移量,这个偏移量大家可以自己计算,我是这样计算的

mMenu.setTranslationX(mMenuWidth + getScrollX() - (mMenuWidth/2)*(1.0f-scale));

设置完Menu后,我们再来设置Content,Content的大小是从1.0缩小到0.7,因此我们这样写

mContent.setScaleX(1 - 0.3f*scale);
mContent.setPivotX(0);
mContent.setScaleY(1.0f - 0.3f * scale);

其中mContent.setPivotX(0)是让Content的缩放中心店的X轴坐标为0点

我们可以将这个变化的过程抽取为一个方法

private void slidingMode3(){
mMenu.setTranslationX(mMenuWidth + getScrollX() - (mMenuWidth/2)*(1.0f-scale));
mMenu.setScaleX(0.7f + 0.3f*scale);
mMenu.setScaleY(0.7f + 0.3f*scale);
mMenu.setAlpha(scale);

mContent.setScaleX(1 - 0.3f*scale);
mContent.setPivotX(0);
mContent.setScaleY(1.0f - 0.3f * scale);
}

将这个方法添加到onTouchEvent的ACTION_MOVE和computeScroll中就可以了。

我们看到所有的滑动风格都是在基于第一种基础上,修改Menu或者Content的translationX或者scaleX scaleY的值来决定的,因此我们可以打造各种各样的SlidingMenu来。

以上所述是小编给大家介绍的Android自定义ViewGroup打造各种风格的SlidingMenu的相关知识,希望对大家有所帮助!

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索android_slidingmenu
android_viewgroup
slidingmenu、slidingmenu 使用、android slidingmenu、slidingmenu点击事件、slidingmenu下载,以便于您获取更多的相关知识。

时间: 2024-09-10 14:00:22

Android自定义ViewGroup打造各种风格的SlidingMenu_Android的相关文章

Android自定义ViewGroup打造各种风格的SlidingMenu

上篇给大家介绍QQ5.0侧滑菜单的视频课程,对于侧滑的时的动画效果的实现有了新的认识,似乎打通了任督二脉,目前可以实现任意效果的侧滑菜单了,感谢鸿洋大大!! 用的是HorizontalScrollView来实现的侧滑菜单功能,HorizontalScrollView的好处是为我们解决了滑动功能,处理了滑动冲突问题,让我们使用起来非常方便,但是滑动和冲突处理都是android中的难点,是我们应该掌握的知识点,掌握了这些,我们可以不依赖于系统的API,随心所欲打造我们想要的效果,因此这篇文章我将直接

Android自定义HorizontalScrollView打造超强Gallery效果_Android

自从Gallery被谷歌废弃以后,Google推荐使用ViewPager和HorizontalScrollView来实现Gallery的效果.的确HorizontalScrollView可以实现Gallery的效果,但是HorizontalScrollView存在一个很大的问题,如果你仅是用来展示少量的图片,应该是没问题的,但是如果我希望HorizontalScrollView可以想ViewPager一样,既可以绑定数据集(动态改变图片),还能做到,不管多少图片都不会OOM(ViewPager内

Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38140505 自从Gallery被谷歌废弃以后,Google推荐使用ViewPager和HorizontalScrollView来实现Gallery的效果.的确HorizontalScrollView可以实现Gallery的效果,但是HorizontalScrollView存在一个很大的问题,如果你仅是用来展示少量的图片,应该是没问题的,但是如果我希望HorizontalScr

Android自定义ViewGroup实现标签流容器FlowLayout_Android

本篇文章讲的是Android 自定义ViewGroup之实现标签流式布局-FlowLayout,开发中我们会经常需要实现类似于热门标签等自动换行的流式布局的功能,网上也有很多这样的FlowLayout,但不影响我对其的学习.和往常一样,主要还是想总结一下自定义ViewGroup的开发过程以及一些需要注意的地方. 按照惯例,我们先来看看效果图 一.写代码之前,有几个是问题是我们先要弄清楚的: 1.什么是ViewGroup:从名字上来看,它可以被翻译为控件组,言外之意是ViewGroup内部包含了许

Android 自定义 HorizontalScrollView 打造多图片OOM 的横向滑动效果(实例代码)

自从Gallery被谷歌废弃以后,Google推荐使用ViewPager和HorizontalScrollView来实现Gallery的效果.的确HorizontalScrollView可以实现Gallery的效果,但是HorizontalScrollView存在一个很大的问题,如果你仅是用来展示少量的图片,应该是没问题的,但是如果我希望HorizontalScrollView可以想ViewPager一样,既可以绑定数据集(动态改变图片),还能做到,不管多少图片都不会OOM(ViewPager内

viewgroup 传参-android自定义ViewGroup的问题

问题描述 android自定义ViewGroup的问题 在名为Demo的activity中用到了一个继承ViewGroup的类MyView来布局,具体是这样的 在Demo的布局文件xml中, 在Demo的代码中 MyView scroll = (MyView) findViewById(R.id.view1); 在MyView中的构造函数 public MyView(Context context, AttributeSet attrs) { //各类操作 } 问题是Demo需要给scroll传

Android自定义ViewGroup实现绚丽的仿支付宝咻一咻雷达脉冲效果_Android

去年春节的时候支付宝推行的集福娃活动着实火的不能再火了,更给力的是春晚又可以全民参与咻一咻集福娃活动,集齐五福就可平分亿元大红包,只可惜没有敬业福--那时候在家没事写了个咻一咻插件,只要到了咻一咻的时间点插件就可以自动的点击咻一咻来咻红包,当时只是纯粹练习这部分技术代码没有公开,后续计划写篇关于插件这方面的文章,扯远了(*^__^*) --我们知道在支付宝的咻一咻页面有个雷达扩散的动画效果,当时感觉动画效果非常棒,于是私下尝试着实现了类似的效果,后来在github发现有大神也写有类似效果,于是读

Android UI设计系列之自定义ViewGroup打造通用的关闭键盘小控件ImeObserverLayout(9)_Android

转载请注明出处:http://blog.csdn.net/llew2011/article/details/51598682 我们平时开发中总会遇见一些奇葩的需求,为了实现这些需求我们往往绞尽脑汁有时候还茶不思饭不香的,有点夸张了(*^__^*)--我印象最深的一个需求是在一段文字中对部分词语进行加粗显示.当时费了不少劲,不过还好,这个问题最终解决了,有兴趣的童靴可以看一下:Android UI设计之<六>使用HTML标签,实现在TextView中对部分文字进行加粗显示. 之前产品那边提了这样

Android自定义ViewGroup实现受边界限制的滚动操作(3)_Android

上一篇文章<自定义viewgroup(2)>地址:http://www.jb51.net/article/100610.htm 代码 package com.example.libingyuan.horizontallistview.ScrollViewGroup; import android.content.Context; import android.util.AttributeSet; import android.util.DisplayMetrics; import androi