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侧边栏也就和大家分享到这




0 0