原文:http://blog.csdn.net/lmj623565791/article/details/39257409
博主的上一篇文章也參考下:Android 自定義控件打造史上最簡單的側滑菜單
1、原理分析
差距還是蠻大的
區別1、QQ的內容區域會伴隨菜單的出現而縮小
區別2、QQ的側滑菜單給人的感覺是隱藏在內容的後面,而不是拖出來的感覺
區別3、QQ的側滑菜單有一個縮放以及透明度的效果~
那麼我們如何能做到呢:
對於區別1:這個好辦,我們可以在滑動的時候,不斷的改變內容區域的大小;如何改變呢?我們在菜單出現的整個過程中,不斷記錄菜單顯示的寬度與其總寬度的比值,是個從0到1的過程,然後把0~1轉化爲1~0.7(假設內容區域縮小至0.7);不斷去縮小內容區域;
對於區別3:也比較好辦,上面已經可以得到0到1的這個值了,那麼縮放和透明度的動畫就不在話下了;
對於區別2:我們使用的HorizontalScrollView,然後水平放置了菜單和內容,如何讓菜單可以隱藏到內容的後面呢?其實也比較簡單,在菜單出現的過程中,不斷設置菜單的x方向的偏移量;0的時候完全隱藏,0.3的時候,隱藏x方向偏移量爲0.7個寬度,類推~~~
好了,分析完畢,那麼對於這些動畫用什麼實現最好呢?
好了,分析完畢,那麼對於這些動畫用什麼實現最好呢?
想都不用想,屬性動畫哈,如果你對屬性動畫不瞭解,可以參考:
Android 屬性動畫(Property Animation) 完全解析 (上)
和 Android 屬性動畫(Property Animation) 完全解析 (下)
2、實現
1、初步的代碼
佈局文件神馬的,都和上一篇一模一樣,這裏就不重複貼代碼了,不瞭解的,先看下上一篇;
先看一下上一篇我們已經實現的完整代碼:
package com.example.zhy_slidingmenu;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.zhy.utils.ScreenUtils;
public class SlidingMenu extends HorizontalScrollView
{
/**
* 屏幕寬度
*/
private int mScreenWidth;
/**
* dp
*/
private int mMenuRightPadding;
/**
* 菜單的寬度
*/
private int mMenuWidth;
private int mHalfMenuWidth;
private boolean isOpen;
private boolean once;
public SlidingMenu(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
mScreenWidth = ScreenUtils.getScreenWidth(context);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SlidingMenu, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.SlidingMenu_rightPadding:
// 默認50
mMenuRightPadding = a.getDimensionPixelSize(attr,
(int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 50f,
getResources().getDisplayMetrics()));// 默認爲10DP
break;
}
}
a.recycle();
}
public SlidingMenu(Context context)
{
this(context, null, 0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
/**
* 顯示的設置一個寬度
*/
if (!once)
{
LinearLayout wrapper = (LinearLayout) getChildAt(0);
ViewGroup menu = (ViewGroup) wrapper.getChildAt(0);
ViewGroup content = (ViewGroup) wrapper.getChildAt(1);
mMenuWidth = mScreenWidth - mMenuRightPadding;
mHalfMenuWidth = mMenuWidth / 2;
menu.getLayoutParams().width = mMenuWidth;
content.getLayoutParams().width = mScreenWidth;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
super.onLayout(changed, l, t, r, b);
if (changed)
{
// 將菜單隱藏
this.scrollTo(mMenuWidth, 0);
once = true;
}
}
@Override
public boolean onTouchEvent(MotionEvent ev)
{
int action = ev.getAction();
switch (action)
{
// Up時,進行判斷,如果顯示區域大於菜單寬度一半則完全顯示,否則隱藏
case MotionEvent.ACTION_UP:
int scrollX = getScrollX();
if (scrollX > mHalfMenuWidth)
{
this.smoothScrollTo(mMenuWidth, 0);
isOpen = false;
} else
{
this.smoothScrollTo(0, 0);
isOpen = true;
}
return true;
}
return super.onTouchEvent(ev);
}
/**
* 打開菜單
*/
public void openMenu()
{
if (isOpen)
return;
this.smoothScrollTo(0, 0);
isOpen = true;
}
/**
* 關閉菜單
*/
public void closeMenu()
{
if (isOpen)
{
this.smoothScrollTo(mMenuWidth, 0);
isOpen = false;
}
}
/**
* 切換菜單狀態
*/
public void toggle()
{
if (isOpen)
{
closeMenu();
} else
{
openMenu();
}
}
}
利用HorizontalScrollView,監聽了ACTION_UP的事件,當用戶擡起手指時,根據當前菜單顯示的寬度值,判斷是縮回還是完全展開;給用戶提供了一個rightPadding屬性,用於設置菜單離右屏幕的距離;以及對外提供了打開,關閉,切換的幾個方法;具體的講解看下上篇博客了;
2、實現的思路
現在我們開始解決那3個區別,已經選擇了使用屬性動畫,現在決定動畫效果應該加在哪兒?
不用說,我用大腿想一想都應該是在ACTION_MOVE中,是的,ACTION_MOVE中的確可以,不斷獲取當前的getScrollX / mMenuWidth,不斷改變菜單的透明度,縮放,X方向的偏移量;不斷改變內容區域的寬度和高度;
說一下,起初我也是在MOVE中這麼做的,但是呢,出現兩個問題:
1、動畫效果並不是很流暢,特別是菜單,有抖動的效果;
2、用戶擡起後,還需要在UP裏面,繼續未完成的動畫,就是說,你的透明度、縮放神馬的,當用戶擡起以後就需要自動變化了;
於是乎,我就開始換了個方向,既然是SrollView,肯定有一個ScrollChanged方法,功夫不負有心人,真心這麼個方法:
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
{
}
這個方法只要scrollChanged就會觸發,l就是我們需要的scrollX,太讚了~~~
3、動畫比例的計算
我們在onScrollChanged裏面,拿到 l 也就是個getScrollX,即菜單已經顯示的寬度值;
float scale = l * 1.0f / mMenuWidth;
與菜單的寬度做除法運算,在菜單隱藏到顯示整個過程,會得到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、菜單的縮放比例計算
仔細觀察了下QQ,菜單大概縮放變化是0.7~1.0
float leftScale = 1 - 0.3f * scale;
3、菜單的透明度比例:
我們設置爲0.6~1.0;即:0.6f + 0.4f * (1 - scale)
4、菜單的x方向偏移量:
看一下QQ,並非完全從被內容區域覆蓋,還是有一點拖出的感覺,所以我們的偏移量這麼設置:
tranlateX = mMenuWidth * scale * 0.6f ;剛開始還是讓它隱藏一點點~~~
4、完整的實現
@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.6f + 0.4f * (1 - scale));
ViewHelper.setTranslationX(mMenu, mMenuWidth * scale * 0.6f);
ViewHelper.setPivotX(mContent, 0);
ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);
ViewHelper.setScaleX(mContent, rightScale);
ViewHelper.setScaleY(mContent, rightScale);
}
就這麼幾行。這裏屬性動畫用的nineoldandroids爲了保持向下的兼容;主要就是設置了各種動畫,上面都詳細說了~~~
然後,記得把我們的菜單和內容的佈局,單獨聲明出來爲我們的mMenu ,mContent ~沒了,就改了這麼幾行~
3、效果圖
是騾子是馬,拉出來溜溜
菜單欄需要ListView的拖動也是不會衝突了,上篇已經測試了;
關於動畫屬性的範圍:上面介紹的特別清楚,比如內容我們是最小顯示0.8,你要是喜歡0.6,自己去修改一下;包括偏移量,透明度等範圍;
因爲上一篇已經寫了如何把屬性抽取成自定義的屬性;所以這裏就沒有抽取了,不然總覺得是在重複~
嗯,最近還有寫APP的側滑,是這樣的,就是菜單欄完全隱藏在內容區域下面,如果需要這樣需求的:
實現呢,註釋幾行代碼就實現了:
package com.example.zhy_slidingmenu;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.zhy.utils.ScreenUtils;
public class SlidingMenu extends HorizontalScrollView
{
/**
* 屏幕寬度
*/
private int mScreenWidth;
/**
* dp
*/
private int mMenuRightPadding;
/**
* 菜單的寬度
*/
private int mMenuWidth;
private int mHalfMenuWidth;
private boolean isOpen;
private boolean once;
public SlidingMenu(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
mScreenWidth = ScreenUtils.getScreenWidth(context);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SlidingMenu, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.SlidingMenu_rightPadding:
// 默認50
mMenuRightPadding = a.getDimensionPixelSize(attr,
(int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 50f,
getResources().getDisplayMetrics()));// 默認爲10DP
break;
}
}
a.recycle();
}
public SlidingMenu(Context context)
{
this(context, null, 0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
/**
* 顯示的設置一個寬度
*/
if (!once)
{
LinearLayout wrapper = (LinearLayout) getChildAt(0);
ViewGroup menu = (ViewGroup) wrapper.getChildAt(0);
ViewGroup content = (ViewGroup) wrapper.getChildAt(1);
mMenuWidth = mScreenWidth - mMenuRightPadding;
mHalfMenuWidth = mMenuWidth / 2;
menu.getLayoutParams().width = mMenuWidth;
content.getLayoutParams().width = mScreenWidth;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
super.onLayout(changed, l, t, r, b);
if (changed)
{
// 將菜單隱藏
this.scrollTo(mMenuWidth, 0);
once = true;
}
}
@Override
public boolean onTouchEvent(MotionEvent ev)
{
int action = ev.getAction();
switch (action)
{
// Up時,進行判斷,如果顯示區域大於菜單寬度一半則完全顯示,否則隱藏
case MotionEvent.ACTION_UP:
int scrollX = getScrollX();
if (scrollX > mHalfMenuWidth)
{
this.smoothScrollTo(mMenuWidth, 0);
isOpen = false;
} else
{
this.smoothScrollTo(0, 0);
isOpen = true;
}
return true;
}
return super.onTouchEvent(ev);
}
/**
* 打開菜單
*/
public void openMenu()
{
if (isOpen)
return;
this.smoothScrollTo(0, 0);
isOpen = true;
}
/**
* 關閉菜單
*/
public void closeMenu()
{
if (isOpen)
{
this.smoothScrollTo(mMenuWidth, 0);
isOpen = false;
}
}
/**
* 切換菜單狀態
*/
public void toggle()
{
if (isOpen)
{
closeMenu();
} else
{
openMenu();
}
}
}