側滑欄現在已經在APP中已經屢見不鮮了,是以也算是一個很常用的布局了,原理很簡單,今天在這分享一下。
1.自定義一個布局,根據使用者的手勢判斷是否需要滑出側滑欄。
2.在layout中使用上一步的自定義布局。
3.最後在Activity中使用。
一、自定義布局
package com.example.slidinglayout;
import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.RelativeLayout;
public class SlidingLayout extends RelativeLayout implements OnTouchListener {
/**
* 滾動顯示和隐藏左側布局時,手指滑動需要達到的速度。
*/
public static final int SNAP_VELOCITY = 200;
/**
* 螢幕寬度值。
*/
private int screenWidth;
/**
* 右側布局最多可以滑動到的左邊緣。
*/
private int leftEdge = 0;
/**
* 右側布局最多可以滑動到的右邊緣。
*/
private int rightEdge = 0;
/**
* 在被判定為滾動之前使用者手指可以移動的最大值。
*/
private int touchSlop;
/**
* 記錄手指按下時的橫坐标。
*/
private float xDown;
/**
* 記錄手指按下時的縱坐标。
*/
private float yDown;
/**
* 記錄手指移動時的橫坐标。
*/
private float xMove;
/**
* 記錄手指移動時的縱坐标。
*/
private float yMove;
/**
* 記錄手機擡起時的橫坐标。
*/
private float xUp;
/**
* 左側布局目前是顯示還是隐藏。隻有完全顯示或隐藏時才會更改此值,滑動過程中此值無效。
*/
private boolean isLeftLayoutVisible;
/**
* 是否正在滑動。
*/
private boolean isSliding;
/**
* 左側布局對象。
*/
private View leftLayout;
/**
* 右側布局對象。
*/
private View rightLayout;
/**
* 用于監聽側滑事件的View。
*/
private View mBindView;
/**
* 左側布局的參數,通過此參數來重新确定左側布局的寬度,以及更改leftMargin的值。
*/
private MarginLayoutParams leftLayoutParams;
/**
* 右側布局的參數,通過此參數來重新确定右側布局的寬度。
*/
private MarginLayoutParams rightLayoutParams;
/**
* 用于計算手指滑動的速度。
*/
private VelocityTracker mVelocityTracker;
/**
* 重寫SlidingLayout的構造函數,其中擷取了螢幕的寬度。
*
* @param context
* @param attrs
*/
public SlidingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
screenWidth = wm.getDefaultDisplay().getWidth();
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
/**
* 綁定監聽側滑事件的View,即在綁定的View進行滑動才可以顯示和隐藏左側布局。
*
* @param bindView
* 需要綁定的View對象。
*/
public void setScrollEvent(View bindView) {
mBindView = bindView;
mBindView.setOnTouchListener(this);
}
/**
* 将螢幕滾動到左側布局界面,滾動速度設定為30.
*/
public void scrollToLeftLayout() {
new ScrollTask().execute(-30);
}
/**
* 将螢幕滾動到右側布局界面,滾動速度設定為-30.
*/
public void scrollToRightLayout() {
new ScrollTask().execute(30);
}
/**
* 左側布局是否完全顯示出來,或完全隐藏,滑動過程中此值無效。
*
* @return 左側布局完全顯示傳回true,完全隐藏傳回false。
*/
public boolean isLeftLayoutVisible() {
return isLeftLayoutVisible;
}
/**
* 在onLayout中重新設定左側布局和右側布局的參數。
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
// 擷取左側布局對象
leftLayout = getChildAt(0);
leftLayoutParams = (MarginLayoutParams) leftLayout.getLayoutParams();
rightEdge = -leftLayoutParams.width;
// 擷取右側布局對象
rightLayout = getChildAt(1);
rightLayoutParams = (MarginLayoutParams) rightLayout.getLayoutParams();
rightLayoutParams.width = screenWidth;
rightLayout.setLayoutParams(rightLayoutParams);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
createVelocityTracker(event);
if (leftLayout.getVisibility() != View.VISIBLE) {
leftLayout.setVisibility(View.VISIBLE);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 手指按下時,記錄按下時的橫坐标
xDown = event.getRawX();
yDown = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 手指移動時,對比按下時的橫坐标,計算出移動的距離,來調整右側布局的leftMargin值,進而顯示和隐藏左側布局
xMove = event.getRawX();
yMove = event.getRawY();
int moveDistanceX = (int) (xMove - xDown);
int distanceY = (int) (yMove - yDown);
if (!isLeftLayoutVisible && moveDistanceX >= touchSlop
&& (isSliding || Math.abs(distanceY) <= touchSlop)) {
isSliding = true;
rightLayoutParams.rightMargin = -moveDistanceX;
if (rightLayoutParams.rightMargin > leftEdge) {
rightLayoutParams.rightMargin = leftEdge;
}
rightLayout.setLayoutParams(rightLayoutParams);
}
if (isLeftLayoutVisible && -moveDistanceX >= touchSlop) {
isSliding = true;
rightLayoutParams.rightMargin = rightEdge - moveDistanceX;
if (rightLayoutParams.rightMargin < rightEdge) {
rightLayoutParams.rightMargin = rightEdge;
}
rightLayout.setLayoutParams(rightLayoutParams);
}
break;
case MotionEvent.ACTION_UP:
xUp = event.getRawX();
int upDistanceX = (int) (xUp - xDown);
if (isSliding) {
// 手指擡起時,進行判斷目前手勢的意圖,進而決定是滾動到左側布局,還是滾動到右側布局
if (wantToShowLeftLayout()) {
if (shouldScrollToLeftLayout()) {
scrollToLeftLayout();
} else {
scrollToRightLayout();
}
} else if (wantToShowRightLayout()) {
if (shouldScrollToRightLayout()) {
scrollToRightLayout();
} else {
scrollToLeftLayout();
}
}
} else if (upDistanceX < touchSlop && isLeftLayoutVisible) {
scrollToRightLayout();
}
recycleVelocityTracker();
break;
}
if (v.isEnabled()) {
if (isSliding) {
unFocusBindView();
return true;
}
if (isLeftLayoutVisible) {
return true;
}
return false;
}
return true;
}
/**
* 判斷目前手勢的意圖是不是想顯示右側布局。如果手指移動的距離是負數,且目前左側布局是可見的,則認為目前手勢是想要顯示右側布局。
*
* @return 目前手勢想顯示右側布局傳回true,否則傳回false。
*/
private boolean wantToShowRightLayout() {
return xUp - xDown < 0 && isLeftLayoutVisible;
}
/**
* 判斷目前手勢的意圖是不是想顯示左側布局。如果手指移動的距離是正數,且目前左側布局是不可見的,則認為目前手勢是想要顯示左側布局。
*
* @return 目前手勢想顯示左側布局傳回true,否則傳回false。
*/
private boolean wantToShowLeftLayout() {
return xUp - xDown > 0 && !isLeftLayoutVisible;
}
/**
* 判斷是否應該滾動将左側布局展示出來。如果手指移動距離大于螢幕的1/2,或者手指移動速度大于SNAP_VELOCITY,
* 就認為應該滾動将左側布局展示出來。
*
* @return 如果應該滾動将左側布局展示出來傳回true,否則傳回false。
*/
private boolean shouldScrollToLeftLayout() {
return xUp - xDown > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
}
/**
* 判斷是否應該滾動将右側布局展示出來。如果手指移動距離加上leftLayoutPadding大于螢幕的1/2,
* 或者手指移動速度大于SNAP_VELOCITY, 就認為應該滾動将右側布局展示出來。
*
* @return 如果應該滾動将右側布局展示出來傳回true,否則傳回false。
*/
private boolean shouldScrollToRightLayout() {
return xDown - xUp > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
}
/**
* 建立VelocityTracker對象,并将觸摸事件加入到VelocityTracker當中。
*
* @param event
* 右側布局監聽控件的滑動事件
*/
private void createVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
/**
* 擷取手指在右側布局的監聽View上的滑動速度。
*
* @return 滑動速度,以每秒鐘移動了多少像素值為機關。
*/
private int getScrollVelocity() {
mVelocityTracker.computeCurrentVelocity(1000);
int velocity = (int) mVelocityTracker.getXVelocity();
return Math.abs(velocity);
}
/**
* 回收VelocityTracker對象。
*/
private void recycleVelocityTracker() {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
/**
* 使用可以獲得焦點的控件在滑動的時候失去焦點。
*/
private void unFocusBindView() {
if (mBindView != null) {
mBindView.setPressed(false);
mBindView.setFocusable(false);
mBindView.setFocusableInTouchMode(false);
}
}
class ScrollTask extends AsyncTask<Integer, Integer, Integer> {
@Override
protected Integer doInBackground(Integer... speed) {
int rightMargin = rightLayoutParams.rightMargin;
// 根據傳入的速度來滾動界面,當滾動到達左邊界或右邊界時,跳出循環。
while (true) {
rightMargin = rightMargin + speed[0];
if (rightMargin < rightEdge) {
rightMargin = rightEdge;
break;
}
if (rightMargin > leftEdge) {
rightMargin = leftEdge;
break;
}
publishProgress(rightMargin);
// 為了要有滾動效果産生,每次循環使線程睡眠20毫秒,這樣肉眼才能夠看到滾動動畫。
sleep(15);
}
if (speed[0] > 0) {
isLeftLayoutVisible = false;
} else {
isLeftLayoutVisible = true;
}
isSliding = false;
return rightMargin;
}
@Override
protected void onProgressUpdate(Integer... rightMargin) {
rightLayoutParams.rightMargin = rightMargin[0];
rightLayout.setLayoutParams(rightLayoutParams);
unFocusBindView();
}
@Override
protected void onPostExecute(Integer rightMargin) {
rightLayoutParams.rightMargin = rightMargin;
rightLayout.setLayoutParams(rightLayoutParams);
}
}
/**
* 使目前線程睡眠指定的毫秒數。
*
* @param millis
* 指定目前線程睡眠多久,以毫秒為機關
*/
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
二、在xml中使用上面的自定義布局
<com.example.slidinglayout.SlidingLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/slidingLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<RelativeLayout
android:id="@+id/menu"
android:layout_width="270dip"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:background="#00ccff"
android:visibility="invisible" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="This is menu"
android:textColor="#000000"
android:textSize="28sp" />
</RelativeLayout>
<LinearLayout
android:id="@+id/content"
android:layout_width="320dip"
android:layout_height="fill_parent"
android:layout_alignParentRight="true"
android:background="#e9e9e9"
android:orientation="vertical"
android:visibility="visible" >
<Button
android:id="@+id/menuButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Menu" />
<ListView
android:id="@+id/contentList"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" >
</ListView>
</LinearLayout>
</com.example.slidinglayout.SlidingLayout>
三、在Activity中使用
package com.example.slidinglayout;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
public class MainActivity extends Activity {
/**
* 側滑布局對象,用于通過手指滑動将左側的菜單布局進行顯示或隐藏。
*/
private SlidingLayout slidingLayout;
/**
* menu按鈕,點選按鈕展示左側布局,再點選一次隐藏左側布局。
*/
private Button menuButton;
/**
* 放在content布局中的ListView。
*/
private ListView contentListView;
/**
* 作用于contentListView的擴充卡。
*/
private ArrayAdapter<String> contentListAdapter;
/**
* 用于填充contentListAdapter的資料源。
*/
private String[] contentItems = { "Content Item 1", "Content Item 2", "Content Item 3",
"Content Item 4", "Content Item 5", "Content Item 6", "Content Item 7",
"Content Item 8", "Content Item 9", "Content Item 10", "Content Item 11",
"Content Item 12", "Content Item 13", "Content Item 14", "Content Item 15",
"Content Item 16" };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
slidingLayout = (SlidingLayout) findViewById(R.id.slidingLayout);
menuButton = (Button) findViewById(R.id.menuButton);
contentListView = (ListView) findViewById(R.id.contentList);
contentListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
contentItems);
contentListView.setAdapter(contentListAdapter);
// 将監聽滑動事件綁定在contentListView上
slidingLayout.setScrollEvent(contentListView);
menuButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (slidingLayout.isLeftLayoutVisible()) {
slidingLayout.scrollToRightLayout();
} else {
slidingLayout.scrollToLeftLayout();
}
}
});
contentListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String text = contentItems[position];
Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
}
});
}
}
四、效果圖
五、源碼下載下傳
點選打開連結