仿QQ6.0主頁面側滑效果
1.概述
最近一直都在帶實習生做專案,發現自己好久沒有寫部落格了,這幾天更新會比較頻繁,今天玩QQ的時候發現QQ主頁選單滑動效果早就變了,實在忍不住晚上就來實現一下了! 這裡我已經寫得很詳細瞭如果大家還是看不太懂請看視訊講解:http://pan.baidu.com/s/1eS1bdLK
2.實現
2.1. 實現的方式多種多樣
2.1.1 自定義ViewGroup ,處理其onTouch事件
2.1.2 FrameLayout + 手勢處理類GestureDetector
2.2.3 使用Google自帶的DrawerLayout 對其進行修改
2.2.4 繼承自水平滾動HorizontalScrollView
大家可以看一下這個http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0909/6612.html,這種方式繼承自ViewGroup,個人覺得還行但是還是比較繁瑣要處理的東西也比較多,那麼我就用最後一種2.2.4的方式實現,有人說直接去網上下載一個原始碼就可以了,這我就只能GG了。
2.3. 自定義SlidingMenu extends HorizontalScrollView 然後寫好佈局檔案這個和ScrollView的用法一樣,只不過是橫向滾動的
/**
* description:
* 仿QQ6.0主頁面側滑的自定View
* Created by 曾輝 on 2016/11/1.
* QQ:240336124
* Email: [email protected]
* Version:1.0
*/
public class SlidingMenu extends HorizontalScrollView {
public SlidingMenu(Context context) {
super(context);
}
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
2.4. 執行起來之後發現佈局不對,完全亂了明明都是match_parent可是就是不行那麼我們就需要用程式碼指定選單和內容的寬度:
選單的寬度 = 螢幕的寬度 - 自定義的右邊留出的寬度
內容的寬度 = 螢幕的寬度
/**
* description:
* 仿QQ6.0主頁面側滑的自定View
* Created by 曾輝 on 2016/11/1.
* QQ:240336124
* Email: [email protected]
* Version:1.0
*/
public class SlidingMenu extends HorizontalScrollView {
private View mMenuView;
private View mContentView;
private int mMenuWidth;
public SlidingMenu(Context context) {
this(context, null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 獲取自定義的右邊留出的寬度
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.SlidingMenu);
float rightPadding = array.getDimension(
R.styleable.SlidingMenu_rightPadding,dip2px(50));
// 計算選單的寬度 = 螢幕的寬度 - 自定義右邊留出的寬度
mMenuWidth = (int) (getScreenWidth() - rightPadding);
array.recycle();
}
/**
* 把dip 轉成畫素
*/
private float dip2px(int dip) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,dip,getResources().getDisplayMetrics());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 1.獲取根View也就是外層的LinearLayout
ViewGroup container = (ViewGroup) this.getChildAt(0);
int containerChildCount = container.getChildCount();
if(containerChildCount>2){
// 裡面只允許放置兩個佈局 一個是Menu(選單佈局) 一個是Content(主頁內容佈局)
throw new IllegalStateException("SlidingMenu 根佈局LinearLayout下面只允許兩個佈局,選單佈局和主頁內容佈局");
}
// 2.獲取選單和內容佈局
mMenuView = container.getChildAt(0);
mContentView = container.getChildAt(1);
// 3.指定內容和選單佈局的寬度
// 3.1 選單的寬度 = 螢幕的寬度 - 自定義的右邊留出的寬度
mMenuView.getLayoutParams().width = mMenuWidth;
// 3.2 內容的寬度 = 螢幕的寬度
mContentView.getLayoutParams().width = getScreenWidth();
}
/**
* 獲取螢幕的寬度
*/
public int getScreenWidth() {
Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
return dm.widthPixels;
}
}
目前的效果就是可以滑動,並且選單和主頁面內容的佈局寬度正常
2.5 接下來一開始就讓選單滑動到關閉狀態,手指滑動擡起判斷選單開啟和關閉並做相應的處理 onLayout() onTouch() smoothScrollTo(),當手指快速的時候切換選單的狀態利用GestureDetector 手勢處理類:
/**
* description:
* 仿QQ6.0主頁面側滑的自定View
* Created by 曾輝 on 2016/11/1.
* QQ:240336124
* Email: [email protected]
* Version:1.0
*/
public class SlidingMenu extends HorizontalScrollView {
private View mMenuView;
private View mContentView;
private int mMenuWidth;
// 手勢處理類 主要用來處理手勢快速滑動
private GestureDetector mGestureDetector;
// 選單是否開啟
private boolean mMenuIsOpen = false;
public SlidingMenu(Context context) {
this(context, null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 獲取自定義的右邊留出的寬度
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
float rightPadding = array.getDimension(
R.styleable.SlidingMenu_rightPadding, dip2px(50));
// 計算選單的寬度 = 螢幕的寬度 - 自定義右邊留出的寬度
mMenuWidth = (int) (getScreenWidth() - rightPadding);
array.recycle();
// 例項化手勢處理類
mGestureDetector = new GestureDetector(context,new GestureListener());
}
/**
* 把dip 轉成畫素
*/
private float dip2px(int dip) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 1.獲取根View也就是外層的LinearLayout
ViewGroup container = (ViewGroup) this.getChildAt(0);
int containerChildCount = container.getChildCount();
if (containerChildCount > 2) {
// 裡面只允許放置兩個佈局 一個是Menu(選單佈局) 一個是Content(主頁內容佈局)
throw new IllegalStateException("SlidingMenu 根佈局LinearLayout下面只允許兩個佈局,選單佈局和主頁內容佈局");
}
// 2.獲取選單和內容佈局
mMenuView = container.getChildAt(0);
mContentView = container.getChildAt(1);
// 3.指定內容和選單佈局的寬度
// 3.1 選單的寬度 = 螢幕的寬度 - 自定義的右邊留出的寬度
mMenuView.getLayoutParams().width = mMenuWidth;
// 3.2 內容的寬度 = 螢幕的寬度
mContentView.getLayoutParams().width = getScreenWidth();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 處理手指快速滑動
if(mGestureDetector.onTouchEvent(ev)){
return mGestureDetector.onTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
// 手指擡起獲取滾動的位置
int currentScrollX = getScrollX();
if (currentScrollX > mMenuWidth / 2) {
// 關閉選單
closeMenu();
} else {
// 開啟選單
openMenu();
}
return false;
}
return super.onTouchEvent(ev);
}
/**
* 開啟選單
*/
private void openMenu() {
smoothScrollTo(0, 0);
mMenuIsOpen = true;
}
/**
* 關閉選單
*/
private void closeMenu() {
smoothScrollTo(mMenuWidth, 0);
mMenuIsOpen = false;
}
@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);
}
}
/**
* 獲取螢幕的寬度
*/
public int getScreenWidth() {
Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
return dm.widthPixels;
}
private class GestureListener extends GestureDetector.SimpleOnGestureListener{
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 當手指快速滑動時候回撥的方法
Log.e("TAG",velocityX+"");
// 如果選單開啟 並且是向左快速滑動 切換選單的狀態
if(mMenuIsOpen){
if(velocityX<-500){
toggleMenu();
return true;
}
}else{
// 如果選單關閉 並且是向右快速滑動 切換選單的狀態
if(velocityX>500){
toggleMenu();
return true;
}
}
return false;
}
}
/**
* 切換選單的狀態
*/
private void toggleMenu() {
if(mMenuIsOpen){
closeMenu();
}else{
openMenu();
}
}
}
到了這一步之後我們就可以切換選單了,並且處理了手指快速滑動,迫不及待的看下效果
2.6. 實現選單左邊抽屜樣式的動畫效果,監聽滾動回撥的方法onScrollChanged() 這個就非常簡單了一句話就搞定,效果就不看了一起看終極效果吧
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
// l 是 當前滾動的x距離 在滾動的時候會不斷反覆的回撥這個方法
Log.e(TAG,l+"");
mMenuView.setTranslationX(l*0.8f);
}
2.7. 實現選單右邊選單的陰影透明度效果,這個打算在主頁面內容佈局上面加一層陰影,用ImageView即可,那麼我們的內容View就需要換了
/**
* description:
* 仿QQ6.0主頁面側滑的自定View
* Created by 曾輝 on 2016/11/1.
* QQ:240336124
* Email: [email protected]
* Version:1.0
*/
public class SlidingMenu extends HorizontalScrollView {
private static final String TAG = "HorizontalScrollView";
private Context mContext;
// 4.給選單和內容View指定寬高 - 左邊選單View
private View mMenuView;
// 4.給選單和內容View指定寬高 - 選單的寬度
private int mMenuWidth;
// 5.3 手勢處理類 主要用來處理手勢快速滑動
private GestureDetector mGestureDetector;
// 5.3 選單是否開啟
private boolean mMenuIsOpen = false;
// 7(4). 主頁面內容View的佈局包括陰影ImageView
private ViewGroup mContentView;
// 7.給內容新增陰影效果 - 陰影的ImageView
private ImageView mShadowIv;
public SlidingMenu(Context context) {
this(context, null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//4.1 計算左邊選單的寬度
//4.1.1 獲取自定義的右邊留出的寬度
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
float rightPadding = array.getDimension(
R.styleable.SlidingMenu_rightPadding, dip2px(50));
// 4.1.2 計算選單的寬度 = 螢幕的寬度 - 自定義右邊留出的寬度
mMenuWidth = (int) (getScreenWidth() - rightPadding);
array.recycle();
// 5.3 例項化手勢處理類
mGestureDetector = new GestureDetector(context,new GestureListener());
this.mContext = context;
}
/**
* 把dip 轉成畫素
*/
private float dip2px(int dip) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 4.2 指定選單和內容View的寬度
// 4.2.1.獲取根View也就是外層的LinearLayout
ViewGroup container = (ViewGroup) this.getChildAt(0);
int containerChildCount = container.getChildCount();
if (containerChildCount > 2) {
// 裡面只允許放置兩個佈局 一個是Menu(選單佈局) 一個是Content(主頁內容佈局)
throw new IllegalStateException("SlidingMenu 根佈局LinearLayout下面只允許兩個佈局,選單佈局和主頁內容佈局");
}
// 4.2.2.獲取選單和內容佈局
mMenuView = container.getChildAt(0);
// 7.給內容新增陰影效果
// 7.1 先new一個主內容佈局用來放 陰影和LinearLayout原來的內容佈局
mContentView = new FrameLayout(mContext);
ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
// 7.2 獲取原來的內容佈局,並把原來的內容佈局從LinearLayout中異常
View oldContentView = container.getChildAt(1);
container.removeView(oldContentView);
// 7.3 把原來的內容View 和 陰影加到我們新建立的內容佈局中
mContentView.addView(oldContentView);
// 7.3.1 建立陰影ImageView
mShadowIv = new ImageView(mContext);
mShadowIv.setBackgroundColor(Color.parseColor("#99000000"));
mContentView.addView(mShadowIv);
// 7.4 把包含陰影的新的內容View 新增到 LinearLayout中
container.addView(mContentView);
// 4.2.3.指定內容和選單佈局的寬度
// 4.2.3.1 選單的寬度 = 螢幕的寬度 - 自定義的右邊留出的寬度
mMenuView.getLayoutParams().width = mMenuWidth;
// 4.2.3.2 內容的寬度 = 螢幕的寬度
mContentView.getLayoutParams().width = getScreenWidth();
}
/**
* 5.處理手指擡起和快速滑動切換選單
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 5.3 處理手指快速滑動
if(mGestureDetector.onTouchEvent(ev)){
return mGestureDetector.onTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
// 5.1 手指擡起獲取滾動的位置
int currentScrollX = getScrollX();
if (currentScrollX > mMenuWidth / 2) {
// 5.1.1 關閉選單
closeMenu();
} else {
// 5.1.2 開啟選單
openMenu();
}
return false;
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
// l 是 當前滾動的x距離 在滾動的時候會不斷反覆的回撥這個方法
Log.e(TAG,l+"");
// 6. 實現選單左邊抽屜樣式的動畫效果
mMenuView.setTranslationX(l*0.8f);
// 7.給內容新增陰影效果 - 計算梯度值
float gradientValue = l * 1f / mMenuWidth;// 這是 1 - 0 變化的值
// 7.給內容新增陰影效果 - 給陰影的View指定透明度 0 - 1 變化的值
float shadowAlpha = 1 - gradientValue;
mShadowIv.setAlpha(shadowAlpha);
}
/**
* 5.1.2 開啟選單
*/
private void openMenu() {
smoothScrollTo(0, 0);
mMenuIsOpen = true;
}
/**
* 5.1.1 關閉選單
*/
private void closeMenu() {
smoothScrollTo(mMenuWidth, 0);
mMenuIsOpen = false;
}
@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);
}
}
/**
* 獲取螢幕的寬度
*/
public int getScreenWidth() {
Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
return dm.widthPixels;
}
/**
* 5.3 處理手指快速滑動
*/
private class GestureListener extends GestureDetector.SimpleOnGestureListener{
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 當手指快速滑動時候回撥的方法
Log.e(TAG,velocityX+"");
// 5.3.1 如果選單開啟 並且是向左快速滑動 切換選單的狀態
if(mMenuIsOpen){
if(velocityX<0){
toggleMenu();
return true;
}
}else{
// 5.3.2 如果選單關閉 並且是向右快速滑動 切換選單的狀態
if(velocityX>0){
toggleMenu();
return true;
}
}
return false;
}
}
/**
* 切換選單的狀態
*/
private void toggleMenu() {
if(mMenuIsOpen){
closeMenu();
}else{
openMenu();
}
}
}
我們來看一下最後的效果吧,最終程式碼量不是很多啦,需要的請下載原始碼,如果是實現QQ5.0或是酷狗的側滑效果可以自己改改,也可以加我QQ或是留言給我