ViewDragHelper從入門到進階


詳細教程看大神GitHub


[1] 國內大神在GitHub博客上的ViewDragHelper詳解


[2] Android ViewDragHelper完全解析 自定義ViewGroup神器 - Hongyang - 博客頻道 - CSDN.NET

[3]Android應用ViewDragHelper詳解及部分源碼淺析 - 工匠若水 - 博客頻道 - CSDN.NET


番外話


一.View移動的相關方法總結:

1.通過改變view在父View的layout位置來移動,但是隻能移動指定的View:

view.layout(l,t,r,b);

view.offsetLeftAndRight(offset);//同時改變left和right

view.offsetTopAndBottom(offset);//同時改變top和bottom

2.通過改變scrollX和scrollY來移動,但是可以移動所有的子View;

scrollTo(x,y);

scrollBy(xOffset,yOffset);

3.通過改變Canvas繪制的位置來移動View的內容:

canvas.drawBitmap(bitmap, left, top, paint)


二.使用ViewDragHelper來處理移動

1.ViewDragHelper在高版本的v4包(android4.4以上的v4)中

2.它主要用於處理ViewGroup中對子View的拖拽處理

3.它是Google在2013年開發者大會提出的

4.它主要封裝瞭對View的觸摸位置,觸摸速度,移動距離等的檢測和Scroller,通過接口回調的

方式告訴我們;隻需要我們指定是否需要移動,移動多少等;

5.本質是對觸摸事件的解析類;


三.getHeight和getMeasuredHeight的區別:

getMeasuredHeight:隻要view執行完onMeasure方法就能夠獲取到值;

getHeight:隻有view執行完layout才能獲取到值;


四.在自定義ViewGroup的時候,如果對子View的測量沒有特殊的需求,那麼可以繼承系統已有的

佈局(比如FrameLayout),目的是為瞭讓已有的佈局幫我們實行onMeasure;


三個簡單的Demo


實現兩個正方形的平移等效果


[1] TestActivity.java


import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;

import com.itheima74.qqslidemenu.R;

public class TestActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

}

[2] DragLayout.java


public class DragLayout extends FrameLayout {
private View redView;// 紅孩子
private View blueView;// 藍精靈

private ViewDragHelper viewDragHelper;

// 生成父類的構造方法:alt+shift+s->c
public DragLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

public DragLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public DragLayout(Context context) {
super(context);
init();
}

private void init() {
viewDragHelper = ViewDragHelper.create(this, callback);
}

/**
* 當DragLayout的xml佈局的結束標簽被讀取完成會執行該方法,此時會知道自己有幾個子View瞭 一般用來初始化子View的引用
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
redView = getChildAt(0);
blueView = getChildAt(1);
}

// @Override
// protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// //要測量我自己的子View
// // int size = getResources().getDimension(R.dimen.width);//100dp
// // int measureSpec =
// MeasureSpec.makeMeasureSpec(redView.getLayoutParams().width,MeasureSpec.EXACTLY);
// // redView.measure(measureSpec,measureSpec);
// // blueView.measure(measureSpec, measureSpec);
//
// //如果說沒有特殊的對子View的測量需求,可以用如下方法
// measureChild(redView, widthMeasureSpec, heightMeasureSpec);
// measureChild(blueView, widthMeasureSpec, heightMeasureSpec);
// }

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = getPaddingLeft();
int top = getPaddingTop();
redView.layout(left, top, left + redView.getMeasuredWidth(), top
+ redView.getMeasuredHeight());
blueView.layout(left, redView.getBottom(),
left + blueView.getMeasuredWidth(), redView.getBottom()
+ blueView.getMeasuredHeight());
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 讓ViewDragHelper幫我們判斷是否應該攔截
boolean result = viewDragHelper.shouldInterceptTouchEvent(ev);
return result;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// 將觸摸事件交給ViewDragHelper來解析處理
viewDragHelper.processTouchEvent(event);
return true;
}

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
/**
* 用於判斷是否捕獲當前child的觸摸事件 child: 當前觸摸的子View return: true:就捕獲並解析 false:不處理
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == blueView || child == redView;
}

/**
* 當view被開始捕獲和解析的回調 capturedChild:當前被捕獲的子view
*/
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
// Log.e("tag", "onViewCaptured");
}

/**
* 獲取view水平方向的拖拽范圍,但是目前不能限制邊界,返回的值目前用在手指抬起的時候view緩慢移動的動畫世界的計算上面; 最好不要返回0
*/
@Override
public int getViewHorizontalDragRange(View child) {
return getMeasuredWidth() - child.getMeasuredWidth();
}

/**
* 獲取view垂直方向的拖拽范圍,最好不要返回0
*/
public int getViewVerticalDragRange(View child) {
return getMeasuredHeight() - child.getMeasuredHeight();
};

/**
* 控制child在水平方向的移動 left:
* 表示ViewDragHelper認為你想讓當前child的left改變的值,left=chile.getLeft()+dx dx:
* 本次child水平方向移動的距離 return: 表示你真正想讓child的left變成的值
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (left < 0) {
// 限制左邊界
left = 0;
} else if (left > (getMeasuredWidth() - child.getMeasuredWidth())) {
// 限制右邊界
left = getMeasuredWidth() - child.getMeasuredWidth();
}
return left;
}

/**
* 控制child在垂直方向的移動 top:
* 表示ViewDragHelper認為你想讓當前child的top改變的值,top=chile.getTop()+dy dy:
* 本次child垂直方向移動的距離 return: 表示你真正想讓child的top變成的值
*/
public int clampViewPositionVertical(View child, int top, int dy) {
if (top < 0) {
top = 0;
} else if (top > getMeasuredHeight() - child.getMeasuredHeight()) {
top = getMeasuredHeight() - child.getMeasuredHeight();
}
return top;
};

/**
* 當child的位置改變的時候執行,一般用來做其他子View的伴隨移動 changedView:位置改變的child
* left:child當前最新的left top: child當前最新的top dx: 本次水平移動的距離 dy: 本次垂直移動的距離
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
if (changedView == blueView) {
// blueView移動的時候需要讓redView跟隨移動
redView.layout(redView.getLeft() + dx, redView.getTop() + dy,
redView.getRight() + dx, redView.getBottom() + dy);
} else if (changedView == redView) {
// redView移動的時候需要讓blueView跟隨移動
blueView.layout(blueView.getLeft() + dx,
blueView.getTop() + dy, blueView.getRight() + dx,
blueView.getBottom() + dy);
}

//1.計算view移動的百分比
float fraction = changedView.getLeft()*1f/(getMeasuredWidth()-changedView.getMeasuredWidth());
Log.e("tag", "fraction:"+fraction);
//2.執行一系列的伴隨動畫
executeAnim(fraction);
}

/**
* 手指抬起的執行該方法, releasedChild:當前抬起的view xvel: x方向的移動的速度 正:向右移動, 負:向左移動
* yvel: y方向移動的速度
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
int centerLeft = getMeasuredWidth() / 2
- releasedChild.getMeasuredWidth() / 2;
if (releasedChild.getLeft() < centerLeft) {
// 在左半邊,應該向左緩慢移動
viewDragHelper.smoothSlideViewTo(releasedChild, 0,
releasedChild.getTop());
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
} else {
// 在右半邊,應該向右緩慢移動
viewDragHelper.smoothSlideViewTo(releasedChild,
getMeasuredWidth() - releasedChild.getMeasuredWidth(),
releasedChild.getTop());
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
}
}
};
/**
* 執行伴隨動畫
* @param fraction
*/
private void executeAnim(float fraction){
//fraction: 0 - 1
//縮放
// ViewHelper.setScaleX(redView, 1+0.5f*fraction);
// ViewHelper.setScaleY(redView, 1+0.5f*fraction);
//旋轉
// ViewHelper.setRotation(redView,360*fraction);//圍繞z軸轉
ViewHelper.setRotationX(redView,360*fraction);//圍繞x軸轉
// ViewHelper.setRotationY(redView,360*fraction);//圍繞y軸轉
ViewHelper.setRotationX(blueView,360*fraction);//圍繞z軸轉
//平移
// ViewHelper.setTranslationX(redView,80*fraction);
//透明
// ViewHelper.setAlpha(redView, 1-fraction);

//設置過度顏色的漸變
redView.setBackgroundColor((Integer) ColorUtil.evaluateColor(fraction,Color.RED,Color.GREEN));
// setBackgroundColor((Integer) ColorUtil.evaluateColor(fraction,Color.RED,Color.GREEN));
}

public void computeScroll() {
if (viewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
}
};
}

[3] ColorUtil.java


public class ColorUtil {
public static Object evaluateColor(float fraction, Object startValue,
Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;

int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;

return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
| (int) ((startR + (int) (fraction * (endR - startR))) << 16)
| (int) ((startG + (int) (fraction * (endG - startG))) << 8)
| (int) ((startB + (int) (fraction * (endB - startB))));
}
}

[4]activity_test.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<com.itheima74.qqslidemenu.test.DragLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >

<TextView
android:layout_width="@dimen/width"
android:layout_height="@dimen/width"
android:background="#ff0000" />
<TextView
android:layout_width="@dimen/width"
android:layout_height="@dimen/width"
android:background="#0000ff" />

</com.itheima74.qqslidemenu.test.DragLayout>

</LinearLayout>

[5] menu.xml


<menu xmlns:android="http://schemas.android.com/apk/res/android" >

<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>

</menu>

效果圖:


簡單的仿QQ側滑


1.MainActivity.java


    public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//隱藏標題欄
if (getActionBar() !=null) {
getActionBar().hide();
}

setContentView(R.layout.activity_main);
}
}

2.activity_main.xml文件


<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.SlidingMenu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.myapplication.MainActivity">

<include layout="@layout/layout_menu"/>
<include layout="@layout/layout_main"/>

</com.example.myapplication.SlidingMenu>

3.layout_main.xml文件


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#22B14C"
android:paddingLeft="20dp"
android:paddingTop="50dp" >

<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/head" />

<ListView
android:id="@+id/menu_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
android:listSelector="@android:color/transparent" >
</ListView>

</LinearLayout>


4.layout_main.xml文件


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_layout"
android:background="#ffffff"
android:orientation="vertical" >

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#18B4ED" >

<ImageView
android:layout_width="30dp"
android:layout_marginLeft="15dp"
android:id="@+id/iv_head"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:background="@drawable/head" />
</RelativeLayout>

<ListView android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_listview"
android:listSelector="@android:color/transparent"></ListView>

</LinearLayout>

5.定義一個SlidingMenu,繼承FrameLayout


public class SlidingMenu extends FrameLayout {
private ViewDragHelper viewDragHelper;
private View menuView;
private View mainView;
private float dragRange;
private int width;

public SlidingMenu(@NonNull Context context) {
super(context);
init();
}

public SlidingMenu(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}

public SlidingMenu(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
//簡單的異常處理
if (getChildCount() != 2) {
throw new IllegalArgumentException("SlideMenu only have 2 children!");
}
menuView = getChildAt(0);
mainView = getChildAt(1);
}

private void init() {
viewDragHelper = ViewDragHelper.create(this, callback);
}

/**
* 該方法在onMeasure執行完之後執行,那麼可以在該方法中初始化自己和子View的寬高
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = getMeasuredWidth();
dragRange = width * 0.6f;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {

/**
* 用於判斷是否捕獲當前child的觸摸事件
* child: 當前觸摸的子View
* return: true:就捕獲並解析 false:不處理
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == menuView || child == mainView;
}

/**
* 獲取view水平方向的拖拽范圍,但是目前不能限制邊界,返回的值目前用在手指抬起的時候view緩慢移動的動畫世界的計算上面;
* 最好不要返回0
*/
@Override
public int getViewHorizontalDragRange(View child) {
return (int) dragRange;
}

/**
* 控制child在水平方向的移動 left:
* 表示ViewDragHelper認為你想讓當前child的left改變的值,left=chile.getLeft()+dx dx:
* 本次child水平方向移動的距離 return: 表示你真正想讓child的left變成的值
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == mainView) {
if (left < 0) {
left = 0;//限制mainView的左邊
} else if (left > dragRange) {
left = (int) dragRange;//限制mainView的右邊
}
}

return left;
}

/**
* 控制child在垂直方向的移動 top:
* 表示ViewDragHelper認為你想讓當前child的top改變的值,top=chile.getTop()+dy dy:
* 本次child垂直方向移動的距離 return: 表示你真正想讓child的top變成的值
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
if (changedView == menuView) {
//困住menuView
menuView.layout(0, 0, menuView.getMeasuredWidth(), menuView.getMeasuredHeight());
//讓mainView移動起來
int newLeft = mainView.getLeft() + dx;
if (newLeft < 0) {
newLeft = 0;//限制mainView的左邊
} else if (newLeft > dragRange) {
newLeft = (int) dragRange;//限制mainView的右邊
}
mainView.layout(newLeft, mainView.getTop() + dy,
mainView.getRight() + dx, mainView.getBottom() + dy);
}
}

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
}
};
}

效果圖


仿QQ側滑欄效果(最終版)


[1]Constant.java文件,功能:填充的數據


public interface Constant {
//主要用於給側欄填充數據
public static final String[] sCheeseStrings = {
"Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
"Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
"Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
"Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
"Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
"Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
"Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
"Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
"Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
};
//主要用於給主界面填充數據
public static final String[] NAMES = new String[] { "宋江", "盧俊義", "吳用",
"公孫勝", "關勝", "林沖", "秦明", "呼延灼", "花榮", "柴進", "李應", "朱仝", "魯智深",
"武松", "董平", "張清", "楊志", "徐寧", "索超", "戴宗", "劉唐", "李逵", "史進", "穆弘",
"雷橫", "李俊", "阮小二", "張橫", "阮小五", " 張順", "阮小七", "楊雄", "石秀", "解珍",
" 解寶", "燕青", "朱武", "黃信", "孫立", "宣贊", "郝思文", "韓滔", "彭玘", "單廷珪",
"魏定國", "蕭讓", "裴宣", "歐鵬", "鄧飛", " 燕順", "楊林", "凌振", "蔣敬", "呂方",
"郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鮑旭", "樊瑞", "孔明", "孔亮", "項充",
"李袞", "金大堅", "馬麟", "童威", "童猛", "孟康", "侯健", "陳達", "楊春", "鄭天壽",
"陶宗旺", "宋清", "樂和", "龔旺", "丁得孫", "穆春", "曹正", "宋萬", "杜遷", "薛永", "施恩",
};
}

[2] MyLinearLayout.java,功能:當側欄已經打開的時候,不讓mainView獲得觸摸事件(阻止用戶手動滑動mainView)


public class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context context) {
super(context);
}

public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

private SlidingMenu slidingMenu;

public void setSlidingMenu(SlidingMenu slidingMenu){
this.slidingMenu=slidingMenu;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(slidingMenu!=null && slidingMenu.getCurrentState()== SlidingMenu.DragState.Open){
//如果slideMenu打開則應該攔截並消費掉事件
return true;
}

return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
if(slidingMenu!=null && slidingMenu.getCurrentState()== SlidingMenu.DragState.Open){
if(event.getAction()==MotionEvent.ACTION_UP){
//抬起則應該關閉slideMenu
slidingMenu.close();
}

//如果slideMenu打開則應該攔截並消費掉事件
return true;
}
return super.onTouchEvent(event);
}
}

[3] MainActivity.java,功能:主界面


public class MainActivity extends AppCompatActivity {

private ListView menu_listview, main_listview;
private SlidingMenu slideMenu;
private ImageView iv_head;
private MyLinearLayout my_layout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

initView();
initData();

}

private void initData() {
menu_listview.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, Constant.sCheeseStrings) {
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
TextView textView = (TextView) super.getView(position, convertView, parent);
textView.setTextColor(Color.WHITE);
return textView;
}
});

main_listview.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, Constant.NAMES) {
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
TextView textView = (TextView) (convertView == null ? super.getView(position, convertView, parent) : convertView);

textView.setTextColor(Color.BLACK);
/*
//縮小一半,需要用到導入nineoldandroids-2.4.0.jar
ViewHelper.setScaleX(textView,0.5f);
ViewHelper.setScaleY(textView,0.5f);

//以屬性動畫變大
ViewPropertyAnimator.animate(textView).scaleX(1).setDuration(350).start();
ViewPropertyAnimator.animate(textView).scaleX(2).setDuration(350).start();*/

textView.setScaleX(0.5f);
textView.setScaleY(0.5f);

//以屬性動畫變大
textView.animate().scaleX(1).setDuration(350).start();
textView.animate().scaleY(1).setDuration(350).start();

return textView;
}
});

slideMenu.setOnDragStateChangeListener(new SlidingMenu.OnDragStateChangeListener() {
@Override
public void onOpen() {
//使得menuView的數據隨機變化
menu_listview.smoothScrollToPosition(new Random().
nextInt(menu_listview.getCount()));

}

@Override
public void onClose() {

//如果想在OnDraging下面設置同樣的抖動效果,需要自定義動畫來設置,
//不然iv_head不會回到原位置,會一直向x正軸移動
iv_head.animate().
translationXBy(15).
setInterpolator(new CycleInterpolator(4)).
setDuration(500).
start();
}

@Override
public void onDraging(float fraction) {
//設置iv_head透明度,向右逐漸變透明
iv_head.setAlpha(1-fraction);

/* //加載動畫資源文件
Animation shake = AnimationUtils.loadAnimation(MainActivity.this,
R.anim.widget_shake);
//給組件iv_head設置抖動效果
iv_head.startAnimation(shake);*/

}
});

my_layout.setSlidingMenu(slideMenu);
}

private void initView() {
menu_listview = (ListView) findViewById(R.id.menu_listview);
main_listview = (ListView) findViewById(R.id.main_listview);
slideMenu = (SlidingMenu) findViewById(R.id.slideMenu);
iv_head = (ImageView) findViewById(R.id.iv_head);
my_layout = (MyLinearLayout) findViewById(R.id.my_layout);
}
}

[4] SlidingMenu.java,功能:實現側滑效果以及根據用戶手指速度來判斷打開或者關閉側滑欄


public class SlidingMenu extends FrameLayout {
private ViewDragHelper viewDragHelper;
private View menuView;
private View mainView;
private float dragRange;
private int width;
private FloatEvaluator mFloatEvaluator;//浮點計算器
private IntEvaluator mIntEvaluator;//整點計算器
private ArgbEvaluator mArgbEvaluator;//顏色計算器

//定義狀態常量
enum DragState {
Open, Close;
}

private DragState currentState = DragState.Close;//當前SlideMenu的狀態默認是關閉的

public SlidingMenu(@NonNull Context context) {
super(context);
init();
}

public SlidingMenu(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}

public SlidingMenu(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

/**
* 獲取當前DragState的狀態
* @return
*/
public DragState getCurrentState(){
return currentState;
}

@Override
protected void onFinishInflate() {
super.onFinishInflate();
//簡單的異常處理
if (getChildCount() != 2) {
throw new IllegalArgumentException("SlideMenu only have 2 children!");
}
menuView = getChildAt(0);
mainView = getChildAt(1);
}

private void init() {
viewDragHelper = ViewDragHelper.create(this, callback);
mFloatEvaluator = new FloatEvaluator();
mIntEvaluator = new IntEvaluator();
mArgbEvaluator = new ArgbEvaluator();
}

/**
* 該方法在onMeasure執行完之後執行,那麼可以在該方法中初始化自己和子View的寬高
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = getMeasuredWidth();
dragRange = width * 0.6f;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}

public ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {

/**
* 用於判斷是否捕獲當前child的觸摸事件
* child: 當前觸摸的子View
* return: true:就捕獲並解析 false:不處理
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == menuView || child == mainView;
}

/**
* 獲取view水平方向的拖拽范圍,但是目前不能限制邊界,返回的值目前用在手指抬起的時候view緩慢移動的動畫世界的計算上面;
* 最好不要返回0
*/
@Override
public int getViewHorizontalDragRange(View child) {
return (int) dragRange;
}

/**
* 控制child在水平方向的移動 left:
* 表示ViewDragHelper認為你想讓當前child的left改變的值,left=chile.getLeft()+dx dx:
* 本次child水平方向移動的距離 return: 表示你真正想讓child的left變成的值
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == mainView) {
if (left < 0) {
left = 0;//限制mainView的左邊
} else if (left > dragRange) {
left = (int) dragRange;//限制mainView的右邊
}
}

return left;
}

/**
* 控制child在垂直方向的移動 top:
* 表示ViewDragHelper認為你想讓當前child的top改變的值,top=chile.getTop()+dy dy:
* 本次child垂直方向移動的距離 return: 表示你真正想讓child的top變成的值
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
if (changedView == menuView) {
//困住menuView
menuView.layout(0, 0, menuView.getMeasuredWidth(), menuView.getMeasuredHeight());
//讓mainView移動起來
int newLeft = mainView.getLeft() + dx;
if (newLeft < 0) {
newLeft = 0;//限制mainView的左邊
} else if (newLeft > dragRange) {
newLeft = (int) dragRange;//限制mainView的右邊
}
mainView.layout(newLeft, mainView.getTop() + dy,
mainView.getRight() + dx, mainView.getBottom() + dy);
}

//1.計算滑動的百分比
float fraction = mainView.getLeft() / dragRange;
//2.執行伴隨動畫
executeAnim(fraction);
//3.更改狀態,回調listener的方法
if (fraction == 0 && currentState != DragState.Close) {
//更改狀態為關閉,並回調關閉的方法
currentState = DragState.Close;
if (mListener != null) {
mListener.onClose();
}
} else if (fraction == 1f && currentState != DragState.Open) {
//更改狀態為打開,並回調打開的方法
currentState = DragState.Open;
if (mListener != null) {
mListener.onOpen();
}
}
//將drag的fraction暴漏給外界
if (mListener != null) {
mListener.onDraging(fraction);
}

}

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (mainView.getLeft() < dragRange / 2) {
close();

} else {
//在右半邊
open();
}

//處理用戶的稍微滑動
if(xvel>200 && currentState!=DragState.Open){
open();
}else if (xvel<-200 && currentState!=DragState.Close) {
close();
}

}
};

/**
* 關閉側邊欄
*/
public void close() {
//在左半邊
viewDragHelper.smoothSlideViewTo(mainView, 0, mainView.getTop());
ViewCompat.postInvalidateOnAnimation(SlidingMenu.this);
}

/**
* 打開側邊欄
*/
public void open() {
//在右半邊
viewDragHelper.smoothSlideViewTo(mainView, (int) dragRange, mainView.getTop());
ViewCompat.postInvalidateOnAnimation(SlidingMenu.this);
}

/**
* 執行伴隨動畫,從1縮小到0.8f
*
* @param fraction 0-1
*/
private void executeAnim(float fraction) {
//縮小mainView
mainView.setScaleX(mFloatEvaluator.evaluate(fraction, 1f, 0.8f));
mainView.setScaleY(mFloatEvaluator.evaluate(fraction, 1f, 0.8f));

//移動,放大,以及改變menuView透明度
menuView.setTranslationX(mIntEvaluator.evaluate(fraction,
-menuView.getMeasuredWidth(), 0));

menuView.setScaleX(mFloatEvaluator.evaluate(fraction, 0.5f, 1f));
menuView.setScaleY(mFloatEvaluator.evaluate(fraction, 0.5f, 1f));

menuView.setAlpha(mFloatEvaluator.evaluate(fraction, 0.3f, 1f));

//給SlideMenu的背景添加黑色的遮罩效果
getBackground().setColorFilter((Integer) mArgbEvaluator.
evaluate(fraction, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);
}

@Override
public void computeScroll() {
//如果繼續是這樣的話
if (viewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(SlidingMenu.this);
}
}

private OnDragStateChangeListener mListener;

public void setOnDragStateChangeListener(OnDragStateChangeListener listener) {
mListener = listener;
}

public interface OnDragStateChangeListener {
/**
* 打開的回調
*/
void onOpen();

/**
* 關閉的回調
*/
void onClose();

/**
* 正在拖拽中的回調
*/
void onDraging(float fraction);
}

}

[5] activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.SlidingMenu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg"
android:id="@+id/slideMenu"
tools:context="com.example.myapplication.MainActivity">

<include layout="@layout/layout_menu"/>
<include layout="@layout/layout_main"/>

</com.example.myapplication.SlidingMenu>

[6]layout_main.xml


<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.MyLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_layout"
android:background="#ffffff"
android:orientation="vertical" >

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#18B4ED" >

<ImageView
android:layout_width="30dp"
android:layout_marginLeft="15dp"
android:id="@+id/iv_head"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:background="@drawable/head" />
</RelativeLayout>

<ListView android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_listview"
android:listSelector="@android:color/transparent"/>

</com.example.myapplication.MyLinearLayout>

[7]layout_menu.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingTop="50dp" >
<!--android:background="#22B14C"-->
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/head" />

<ListView
android:id="@+id/menu_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
android:listSelector="@android:color/transparent" >
</ListView>

</LinearLayout>

PS:如果需要在MainActivity中的 slideMenu.setOnDragStateChangeListener(new SlidingMenu.OnDragStateChangeListener() 中的onDraging方法裡面實現mainView中的iv_head頭像抖動效果,就需要自定義效果啦:

1.在anim文件夾下創建兩個xml文件

[1] cycle_7.xml


<?xml version="1.0" encoding="utf-8"?>
<cycleInterpolator
xmlns:android="http://schemas.android.com/apk/res/android"
android:cycles="4"/>

[2]widget_shake.xml


<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="15"
android:interpolator="@anim/cycle_7"/>

[3]在MainActivity中的onDraging加載動畫資源文件並且實現抖動效果


           @Override
public void onDraging(float fraction) {
...

//加載動畫資源文件
Animation shake = AnimationUtils.loadAnimation(MainActivity.this,
R.anim.widget_shake);
//給組件iv_head設置抖動效果
iv_head.startAnimation(shake);
...
}

源碼下載地址:


仿QQ側滑欄的Demo源碼下載地址:

guangzhoushiqu/QQSlideMenu - 碼雲 - 開源中國

0 個評論

要回覆文章請先登錄註冊