轉載請註明出處: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 implements
OnGestureListener {
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隱藏起來
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
detector.onTouchEvent(ev);
return super.onTouchEvent(ev);
}
/**
* 設置子view的寬和高 設置自己的寬和高
*/
@Override
protected 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隱藏
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
smoothScrollTo(mMenuWidth, 0);
}
}
@Override
public boolean onDown(MotionEvent e) {
Log.e("onDown", "YEAYEASYEAOJUOIUEIOAS");
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
// 滑動
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
// Log.e("onScroll", "YEAYEASYEAOJUOIUEIOAS");
return false;
}
// 長按
@Override
public void onLongPress(MotionEvent e) {
Log.e("onLongPress", "YEAYEASYEAOJUOIUEIOAS");
}
// 按,滑動
@Override
public 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的寬和高 設置自己的寬和高
*/
@Override
protected 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隱藏
*/
@Override
protected 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隱藏起來
*/
@Override
public 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
@Override
protected 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() {
@Override
public void run() {
smoothScrollTo(-mMenuWidth, 0);
}
});
見證奇蹟吧少年!記得兩處都要加哦,至於爲什麼,以我目前的知識,還不太說得清楚,附上解決的國外大神帖子:相關鏈接 想不通,因爲一般我們調用post,是在非ui線程調用UI線程的時候使用,而在我們這個Demo裏,我試過去獲取它的線程和mainActivity的線程,都是1,也就是說兩者都是UI線程,那爲什麼可以調用post呢?這也算是一個未解決的問題,希望各位看官有什麼思路多多提出來分享啊,好啦,仿QQ側邊欄也就和大家分享到這