Android 仿QQ侧边栏,自定义view的学习 <Garry进阶(三)>
来源:互联网 发布:java终止线程 编辑:程序博客网 时间:2024/06/07 00:12
转载请注明出处:http://blog.csdn.net/lrs123123/article/details/41896677
恩,从一个神马都不懂的东西小菜鸟到现在懂了点皮毛的码界菜鸟,感慨颇多,不废话,进入正题!
先上最终效果图:
SlidingMenu大家都很熟悉对吧,但是平常可能大家都是直接导入第三方包,搞定,今天,我们自己动手丰衣足食。对于滑动view的移动,从整体布局分析,我们可以有三种实现形式,SlidingDrawer,RelativeLayout,还有就是ViewGroup。对于这三种的分析,在这里就不赘言了,我们先来学习viewGroup中的HorizontalScrollView,来作为自定义空间的主体。
那么我们的思路应该怎么做呢,记住,罗马不是一天建成的,我们要有简单到复杂,不管多奇怪,多复杂的项目,他也是从helloworld变来的
那么,我们先做出普通HorizontalScrollView效果,以唯品为例如下:
先做出唯品会例子的效果吧,我们使用HorizontalScrollView,水平放置菜单和内容,HorizontalScrollView使用的好处就是,只需要监听ACTION_UP就好了,它本身就带有滑动的功能,废话不多说,上代码!
1、布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:garry="http://schemas.android.com/apk/res/com.Garry.myslidingmenudemoa" android:layout_width="match_parent" android:layout_height="match_parent" > <com.Garry.myslidingmenudemoa.MySlidingMenu android:id="@+id/msm" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/taylor" garry:RightPadding="150dp" > <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" > <include layout="@layout/layout_menu" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/myqq" > </LinearLayout> </LinearLayout> </com.Garry.myslidingmenudemoa.MySlidingMenu></RelativeLayout>
一个RelativeLayout,里边放上我们的自定义View, 里边放水平LinearLayout,再放上Menu布局,ok~
里边涉及到一点简单的自定义属性,我们需要新建attr.xml,在里边自定义我们要的属性如下:
<resources> <attr name="RightPadding" format="dimension"></attr> <declare-styleable name="MySlidingMenu"> <attr name="RightPadding"></attr> </declare-styleable></resources>
这样,我们就能在空间里边使用我们自己定义的属性 RightPadding了,
garry:RightPadding="150dp"
2、自定义控件
核心代码~
1.MySlidingMenu
package com.Garry.myslidingmenudemoa;import android.content.Context;import android.content.res.TypedArray;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Log;import android.util.TypedValue;import android.view.GestureDetector;import android.view.GestureDetector.OnGestureListener;import android.view.MotionEvent;import android.view.ViewGroup;import android.view.WindowManager;import android.widget.HorizontalScrollView;import android.widget.LinearLayout;import com.nineoldandroids.view.ViewHelper;public class MySlidingMenu extends HorizontalScrollView implementsOnGestureListener {private LinearLayout mWapper;private ViewGroup mMenu;private ViewGroup mContent;private int mScreenWidth; // 屏幕宽度private int mMenuWidth;private int mMenuRightPadding = 30;private boolean once;private boolean isOpen;private GestureDetector detector = new GestureDetector(this);/** * 当使用自定义属性时调用 * * @param context * @param attrs * @param defStyle */public MySlidingMenu(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// 获取自定义属性TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.MySlidingMenu, defStyle, 0);int n = a.getIndexCount();for (int i = 0; i < n; i++) {int attr = a.getIndex(i);switch (attr) {case R.styleable.MySlidingMenu_RightPadding:mMenuRightPadding = a.getDimensionPixelOffset(attr,(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics()));break;}}// 获取屏幕宽度WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(outMetrics);mScreenWidth = outMetrics.widthPixels;detector.setIsLongpressEnabled(true); // 这句话有多重要}public MySlidingMenu(Context context) {this(context, null); // 一个参数构造方法 去调用 两个参数构造方法}/** * 未使用自定义属性时调用 * * @param context * @param attrs */public MySlidingMenu(Context context, AttributeSet attrs) {this(context, attrs, 0); // 两个参数构造方法调用 三个参数构造方法}/** * 触摸操作 通过设置偏移量 将menu隐藏起来 */@Overridepublic boolean onTouchEvent(MotionEvent ev) {detector.onTouchEvent(ev);return super.onTouchEvent(ev);}/** * 设置子view的宽和高 设置自己的宽和高 */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (!once) {mWapper = (LinearLayout) getChildAt(0);mMenu = (ViewGroup) mWapper.getChildAt(0);mContent = (ViewGroup) mWapper.getChildAt(1);mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth- mMenuRightPadding;mContent.getLayoutParams().width = mScreenWidth;once = true;}super.onMeasure(widthMeasureSpec, heightMeasureSpec);}/** * 设置子view位置 * * @param changed * 通过设置偏移量 将mMenu隐藏 */@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (changed) {smoothScrollTo(mMenuWidth, 0);}}@Overridepublic boolean onDown(MotionEvent e) {Log.e("onDown", "YEAYEASYEAOJUOIUEIOAS");return false;}@Overridepublic void onShowPress(MotionEvent e) {}@Overridepublic boolean onSingleTapUp(MotionEvent e) {return false;}// 滑动@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,float distanceY) {// Log.e("onScroll", "YEAYEASYEAOJUOIUEIOAS");return false;}// 长按@Overridepublic void onLongPress(MotionEvent e) {Log.e("onLongPress", "YEAYEASYEAOJUOIUEIOAS");}// 按,滑动@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) {Log.e("onFling", "YEAYEASYEAOJUOIUEIOAS");Log.e("VELVELVEL", "is" + velocityX); // 测试得知,当向左滑动,velocityX为负,向右为正if (velocityX > 10.0) {smoothScrollTo(-mMenuWidth,0);} else {smoothScrollTo(mMenuWidth, 0);}return false;}}
要不是我把GestureDeletor,代码量是很少的,不信? 对比下吧
package com.Garry.myslidingmenudemoa;import android.content.Context;import android.content.res.TypedArray;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.TypedValue;import android.view.MotionEvent;import android.view.ViewGroup;import android.view.WindowManager;import android.widget.HorizontalScrollView;import android.widget.LinearLayout;public class MySlidingMenu extends HorizontalScrollView {private LinearLayout mWapper;private ViewGroup mMenu;private ViewGroup mContent;private int mScreenWidth; // 屏幕宽度private int mMenuWidth;private int mMenuRightPadding = 30;private boolean once;private boolean isOpen;/** * 当使用自定义属性时调用 * * @param context * @param attrs * @param defStyle */public MySlidingMenu(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// 获取自定义属性TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.MySlidingMenu, defStyle, 0);int n = a.getIndexCount();for (int i = 0; i < n; i++) {int attr = a.getIndex(i);switch (attr) {case R.styleable.MySlidingMenu_RightPadding:mMenuRightPadding = a.getDimensionPixelOffset(attr,(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics()));break;}}// 获取屏幕宽度WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(outMetrics);mScreenWidth = outMetrics.widthPixels;}public MySlidingMenu(Context context) {this(context, null); // 一个参数构造方法 去调用 两个参数构造方法}/** * 未使用自定义属性时调用 * * @param context * @param attrs */public MySlidingMenu(Context context, AttributeSet attrs) {this(context, attrs, 0); // 两个参数构造方法调用 三个参数构造方法}/** * 设置子view的宽和高 设置自己的宽和高 */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (!once) {mWapper = (LinearLayout) getChildAt(0);mMenu = (ViewGroup) mWapper.getChildAt(0);mContent = (ViewGroup) mWapper.getChildAt(1);mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth- mMenuRightPadding;mContent.getLayoutParams().width = mScreenWidth;once = true;}super.onMeasure(widthMeasureSpec, heightMeasureSpec);}/** * 设置子view位置 * * @param changed * 通过设置偏移量 将mMenu隐藏 */@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (changed) {scrollTo(mMenuWidth, 0);}}/** * 触摸操作 通过设置偏移量 将menu隐藏起来 */@Overridepublic boolean onTouchEvent(MotionEvent ev) {int action = ev.getAction();switch (action) {case MotionEvent.ACTION_UP:int scrollX = getScrollX();if (scrollX >= mMenuWidth / 2) {this.smoothScrollTo(mMenuWidth, 0);isOpen = false;} else {this.smoothScrollTo(0, 0);isOpen = true;}return true;}return super.onTouchEvent(ev);}}
好啦,简单吧,就不解释了,注释说的挺仔细了
至此,已经基本可以实现唯品会那种简单的策划效果了,这时,我们回头来看看和最终结果比较,还是差挺多的
1、最终结果可以缩小放大
2、最终侧滑菜单有透明度的效果
3、进阶:实现思路
恩,动画效果,恩,选属性动画,那么应该加在哪里呢,ACTION_MOVE?可以,不过看了大神的博客,最终我们选在了流畅度搞得ScrollView里的天然方法:ScrollChanged
@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);float scale = l * 1.0f / mMenuWidth;float leftScale = 1 - 0.3f * scale;float rightScale = 0.8f + scale * 0.2f;ViewHelper.setScaleX(mMenu, leftScale);ViewHelper.setScaleY(mMenu, leftScale);ViewHelper.setAlpha(mMenu, 0.2f+0.8f*(1 - scale));//透明度设置ViewHelper.setPivotX(mContent, 0);ViewHelper.setPivotY(mContent, mContent.getHeight());ViewHelper.setScaleX(mContent, rightScale);ViewHelper.setScaleY(mContent,rightScale);}
恩,这部分主要参考大神的思路,直接贴吧 (以下为摘录)
我们在onScrollChanged里面,拿到 l 也就是个getScrollX,即菜单已经显示的宽度值;
与菜单的宽度做除法运算,在菜单隐藏到显示整个过程,会得到1.0~0.0这么个变化的区间;
有了这个区间,就可以根据这个区间设置动画了;
1 首先是内容区域的缩放比例计算:
我们准备让在菜单出现的过程中,让内容区域从1.0~0.8进行变化~~
那么怎么把1.0~0.0转化为1.0~0.8呢,其实很简单了:
float rightScale = 0.8f + scale * 0.2f; (scale 从1到0 )
接下来还有3个动画:
2 菜单的缩放比例计算
菜单大概缩放变化是0.7~1.0
float leftScale = 1 - 0.3f * scale;
3 菜单的透明度比例:
我们设置为0.2~1.0;即:0.2f + 0.8f * (1 - scale)
4 菜单的x方向偏移量:
看最终结果,并非完全从被内容区域覆盖,还是有一点拖出的感觉,所以我们的偏移量这么设置:
tranlateX = mMenuWidth * scale * 0.6f ;刚开始还是让它隐藏一点
4、未解决部分
呼,对比QQ我们还需要在加手势,接下来就有点头疼,这搞了我很久很久,不过最终还是憋出来了
完整代码最上边有了,各位客官请往楼上挪位,使用GestureDeletor手势类,如果要介绍估计又要多一篇博文来介绍了,等以后有时间再写吧
有几个要注意的点: 记得复写onTouchEvent方法,在方做申明deletor.onTouchEvent(MotionEvent) 还有一个很多人刚开始用容易忽略的,记得要加上一句:detector.setIsLongpressEnabled(true); 你以为这样就好了么?太天真了,还有个大坑等着你呢!
相信很多童鞋认真的写完一运行就会发现,额,滑动没效果!,怎么回事?debug!,设置log,怎么折腾也好,smoothScrollTo就是一直无效!为什么呢!
起先我也困扰很久,知道谷歌告诉我答案,不得不说,国外的大神还是比国内叼,加上这样一段代码后,奇迹般的复活了!很简单一段代码
this.post(new Runnable() { @Overridepublic void run() {smoothScrollTo(-mMenuWidth, 0); }});
见证奇迹吧少年!记得两处都要加哦,至于为什么,以我目前的知识,还不太说得清楚,附上解决的国外大神帖子:相关链接 想不通,因为一般我们调用post,是在非ui线程调用UI线程的时候使用,而在我们这个Demo里,我试过去获取它的线程和mainActivity的线程,都是1,也就是说两者都是UI线程,那为什么可以调用post呢?这也算是一个未解决的问题,希望各位看官有什么思路多多提出来分享啊,好啦,仿QQ侧边栏也就和大家分享到这
- Android 仿QQ侧边栏,自定义view的学习 <Garry进阶(三)>
- Android实现仿qq侧边栏效果
- Android实现仿qq侧边栏效果
- 仿QQ侧边栏滑动的实现
- android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
- android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
- android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索
- Android进阶三:自定义View
- 【自定义View】2.Android滑动侧边栏SlidingMenu的原理
- iOS-仿QQ侧边栏
- iOS-仿QQ侧边栏
- Android自定义View之侧边栏初探
- # 仿QQ底部导航栏的自定义view
- 自定义View之案列篇(三):仿QQ小红点
- Android 类似QQ的侧边栏
- Android进阶之自定义view(三)
- 【Android进阶之自定义View(三)】
- android自定义View 之仿QQ消息头像
- 26. Dubbo原理解析-监控
- ajax延迟加载数据
- Golang部份特性的C++对比实现
- android控件开发之progress
- java对象的强引用,软引用,弱引用和虚引用
- Android 仿QQ侧边栏,自定义view的学习 <Garry进阶(三)>
- 编写 ATL ActiveX 控件
- Android学习笔记(三)——理解android中的Menu
- RabbitMQ概念及环境搭建(二)RabbitMQ Broker管理
- MySQL5.6.12促成CPU的使用率 2000%的原因
- Eclipse中ndk配置
- C++技术问题总结-第14篇 常用设计模式及其应用场景
- .htaccess和license文件编写
- 织梦dedecms不能给图片加水印的一个原因