Android側滑菜單完整詳細樣本(改進版)
MainActivity如下:
package cc.cd;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;import android.app.Activity;import android.content.Context;/** * Demo描述: * 將Patience5中使用的側滑菜單封裝為一個自訂控制項從而便於複用 * * 該樣本在Patience5的不同之處: * 1 Patience5中採用的是LinearLayout布局,而該自訂控制項繼承自RelativeLayout * 2 在Patience5中是不斷改變menuView的leftMargin而在此處是不斷改變contentView的 * rightMargin.這一點在閱讀代碼時要注意. * * 參考資料: * http://blog.csdn.net/guolin_blog/article/details/8744400 * Thank you very much */public class MainActivity extends Activity {private Context mContext;private ListView mContentListView; private Button mContentMenuButton; private SlidingMenuRelativeLayout mSlidingMenuRelativeLayout; private String [] listViewItems=new String [20]; private ArrayAdapter mArrayAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);init();}private void init(){mContext=this;mContentListView=(ListView) findViewById(R.id.contentListView);mContentMenuButton=(Button) findViewById(R.id.contentMenuButton);mContentMenuButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {boolean isMenuVisible=mSlidingMenuRelativeLayout.isMenuVisible();if (isMenuVisible) {mSlidingMenuRelativeLayout.scrollToContent();} else {mSlidingMenuRelativeLayout.scrollToMenu();}}});mSlidingMenuRelativeLayout=(SlidingMenuRelativeLayout) findViewById(R.id.slidingMenuRelativeLayout);//滑動事件綁定在contentListView上mSlidingMenuRelativeLayout.setBindView(mContentListView);initListViewData();mArrayAdapter=new ArrayAdapter(mContext, android.R.layout.simple_list_item_1, listViewItems);mContentListView.setAdapter(mArrayAdapter);mContentListView.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView arg0, View arg1, int position, long arg3) {String item = listViewItems[position];Toast.makeText(mContext, item, Toast.LENGTH_SHORT).show();}});}private void initListViewData(){String temp=null;for (int i = 0; i < 20; i++) {temp="This is "+i;listViewItems[i]=temp;}}}
SlidingMenuRelativeLayout如下:
package cc.cd;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.WindowManager;import android.widget.Button;import android.widget.RelativeLayout;import android.widget.Toast;/** * 樣本說明: * 1 該自訂控制項繼承自RelativeLayout * 2 在布局檔案中有兩部分menuView和contentView.因為是RelativeLayout布局 * 所以這兩者是重疊的. * 3 通過不斷改變contentView的rightMargin值來顯示和隱藏menuView * 4 請注意在xml布局檔案中設定了contentView對齊為layout_alignParentRight * 設定了menuView對齊為 android:layout_alignParentLeft. * 所以注意代碼: * //設定contentView的最小和最大rightMargin值 * contentParamsMinRightMargin=-mMenuLayoutParams.width; * contentParamsMaxRightMargin=0; * 這個還是挺巧妙的.因為contentView是相對於父控制項靠右對齊的.所以在原始狀態下 * 他的rightMargin的值為0.當手指按在contentView上滑向螢幕的右側時可以不斷減小 * 它的rightMargin,從而導致contentView移向螢幕的右側本來被其遮蓋的menuView * 也就隨之顯現. * 所以contentView的最大rightMargin值=0,這個也就是我們進入App後看到的: * 顯示了contentView,遮蓋了menuView.它的rightMargin=0;與父控制項右側對齊. * 當contentView移向螢幕的右邊時,它的rightMargin在逐漸減小直到rightMargin * 的絕對值對於menuView的寬度. * * * 代碼細節: * 1 mMenuLayoutParams和mContentLayoutParams都是 * MarginLayoutParams類型的,因為menuView和conentView的父控制項 * 是自訂的SlidingMenuRelativeLayout,而不是常用的系統XXXLayout * 2 在手指在ListView上滑動然後抬起,有時會出現ListView的item被按下且一直沒有 * 彈起的情況.造成該現象的原因暫時不明,但可用unFocusBindView()方法使得 * ListView失去焦點.該方法在樣本中兩次被調用.可以將其注釋後觀察效果. * 關於這點,在代碼中仍然存在小bug.需以後繼續最佳化. */public class SlidingMenuRelativeLayout extends RelativeLayout {private int screenWidth;private View contentView;private View menuView;private View mBindView;private float xDown;private float xMove;private float xUp;private float yDown;private float yMove;private float yUp;private Button mButton;private Context mContext;// 當前是否在滑動private boolean isSliding;// 在被判定為滾動之前使用者手指可移動的最大值private int scaledTouchSlop;// menu是否可見的標誌位,該值在滑動過程中無效.// 只有在滑動結束後,完全顯示或隱藏menu時才會更改此值private boolean isMenuVisible = false;private int contentParamsMaxRightMargin = 0;private int contentParamsMinRightMargin = 0;// 速度追蹤private VelocityTracker mVelocityTracker;// 閾值public static final int VELOCITY_THRESHOLD = 200;// TAGprivate final static String TAG = "SlidingMenuRelativeLayout";// menu的布局LayoutParamsprivate MarginLayoutParams mMenuLayoutParams;// content的布局LayoutParamsprivate MarginLayoutParams mContentLayoutParams;public SlidingMenuRelativeLayout(Context context) {super(context);}public SlidingMenuRelativeLayout(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public SlidingMenuRelativeLayout(Context context, AttributeSet attrs,int defStyle) {super(context, attrs, defStyle);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (changed) {// 初始化contentViewcontentView = getChildAt(1);mContentLayoutParams = (MarginLayoutParams) contentView.getLayoutParams();// 將contentView的寬度設定為螢幕的寬度mContentLayoutParams.width = screenWidth;contentView.setLayoutParams(mContentLayoutParams);// 初始化menuViewmenuView = getChildAt(0);mMenuLayoutParams = (MarginLayoutParams) menuView.getLayoutParams();// 設定contentView的最小和最大rightMargin值.contentParamsMinRightMargin = -mMenuLayoutParams.width;contentParamsMaxRightMargin = 0;mButton = (Button) menuView.findViewById(R.id.menuButton);mButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {Toast.makeText(mContext, "Hello", Toast.LENGTH_SHORT).show();}});}}private void init(Context context) {mContext = context;// 擷取螢幕寬度WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);screenWidth = windowManager.getDefaultDisplay().getWidth();// 擷取ScaledTouchSlopscaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}/** * 註冊處理Touch事件的View 在改樣本中處理了ListView的Touch來顯示和隱藏menuView的. * 在實際開發中可以依據需求設定其他為其他控制項 */public void setBindView(View bindView) {mBindView = bindView;mBindView.setOnTouchListener(new TouchListenerImpl());}// 使BindView失去焦點public void unFocusBindView() {if (mBindView != null) {mBindView.setPressed(false);mBindView.setFocusable(false);mBindView.setFocusableInTouchMode(false);}}private class TouchListenerImpl implements OnTouchListener {@Overridepublic boolean onTouch(View v, MotionEvent event) {// 開始速度追蹤startVelocityTracker(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:xDown = event.getRawX();yDown = event.getRawY();break;case MotionEvent.ACTION_MOVE:xMove = event.getRawX();yMove = event.getRawY();int distanceX = (int) (xMove - xDown);int distanceY = (int) (yMove - yDown);// 手指滑向螢幕右側,distanceX為正數if (!isMenuVisible&& distanceX >= scaledTouchSlop&& (isSliding || Math.abs(distanceY) <= scaledTouchSlop)) {isSliding = true;mContentLayoutParams.rightMargin = -distanceX;// 處理越界的情況if (mContentLayoutParams.rightMargin < contentParamsMinRightMargin) {mContentLayoutParams.rightMargin = contentParamsMinRightMargin;}// 設定contentView的LayoutParamscontentView.setLayoutParams(mContentLayoutParams);}// 手指滑向螢幕左側,distanceX為負數if (isMenuVisible && -distanceX >= scaledTouchSlop) {isSliding = true;mContentLayoutParams.rightMargin = contentParamsMinRightMargin - distanceX;// 處理越界的情況if (mContentLayoutParams.rightMargin > contentParamsMaxRightMargin) {mContentLayoutParams.rightMargin = contentParamsMaxRightMargin;}// 設定contentView的LayoutParamscontentView.setLayoutParams(mContentLayoutParams);}break;case MotionEvent.ACTION_UP:xUp = event.getRawX();int upDistanceX = (int) (xUp - xDown);if (isSliding) {// 判斷手勢意圖想顯示menuif (wantToShowMenu()) {// 判斷是否顯示menuif (shouldScrollToMenu()) {scrollToMenu();} else {scrollToContent();}}// 判斷手勢意圖想顯示contentif (wantToShowContent()) {// 判斷是否顯示contentif (shouldScrollToContent()) {scrollToContent();} else {scrollToMenu();}}} else {// 當完全顯示菜單介面時,點擊僅能看到的contentView可將其完全顯示if (upDistanceX < scaledTouchSlop && isMenuVisible) {scrollToContent();}}// 終止速度追蹤stopVelocityTracker();break;default:break;}/** * 在處理完DOWN,MOVE,UP後進行該if...else判斷. * 1 如果不是在滑動狀態,則返回false.否則ListView無法滑動且其Item無法點擊. * 2 若在滑動,則讓ListView失去焦點. */if (!isSliding) {return false;} else {unFocusBindView();}return true;}}/** * 判斷當前手勢是否想顯示菜單Menu * 判斷條件: * 1 抬起座標大於按下座標 * 2 menu本身不可見 */private boolean wantToShowMenu() {return ((xUp - xDown > 0) && (!isMenuVisible));}/** * 判斷是否應該將menu完整顯示出來 * 判斷條件: 滑動距離大於菜單的二分之一 * 或者滑動速度大於速度閾值VELOCITY_THRESHOLD */private boolean shouldScrollToMenu() {return ((xUp - xDown > mMenuLayoutParams.width / 2) || (getScrollVelocity() > VELOCITY_THRESHOLD));}/** * 將螢幕滾動到menu.即將menu完整顯示. */public void scrollToMenu() {new ScrollAsyncTask().execute(-30);}/** * 判斷當前手勢是否想顯示菜單Content * 判斷條件: * 1 抬起座標小於按下座標 * 2 menu本身可見 */private boolean wantToShowContent() {return ((xUp - xDown < 0) && (isMenuVisible));}/** * 判斷是否應該將content完整顯示出來 * 判斷條件: 滑動距離大於菜單的二分之一 * 或者滑動速度大於速度閾值VELOCITY_THRESHOLD */private boolean shouldScrollToContent() {return ((xDown - xUp > mMenuLayoutParams.width / 2) || (getScrollVelocity() > VELOCITY_THRESHOLD));}/** * 將螢幕滾動到content.即將content完整顯示 */public void scrollToContent() {new ScrollAsyncTask().execute(30);}public boolean isMenuVisible() {return isMenuVisible;}/** * 開始速度追蹤 */private void startVelocityTracker(MotionEvent event) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);}/** * 擷取在content上X方向的手指滑動速度 */private int getScrollVelocity() {// 設定VelocityTracker單位.1000表示1秒時間內運動的像素mVelocityTracker.computeCurrentVelocity(1000);// 擷取在1秒內X方向所滑動像素值int xVelocity = (int) mVelocityTracker.getXVelocity();return Math.abs(xVelocity);}/** * 終止速度追蹤 */private void stopVelocityTracker() {if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}/** * 利用非同步任務不斷修改contentView的LayoutParams中的rightMargin從而達到 contentView視圖移動的效果 */private class ScrollAsyncTask extends AsyncTask {@Overrideprotected Integer doInBackground(Integer... speed) {int contentLayoutParamsRightMargin = mContentLayoutParams.rightMargin;while (true) {// 每次變化的speedcontentLayoutParamsRightMargin = contentLayoutParamsRightMargin + speed[0];// 若越界,則處理越界且跳出迴圈if (contentLayoutParamsRightMargin > contentParamsMaxRightMargin) {contentLayoutParamsRightMargin = contentParamsMaxRightMargin;break;}// 若越界,則處理越界且跳出迴圈if (contentLayoutParamsRightMargin < contentParamsMinRightMargin) {contentLayoutParamsRightMargin = contentParamsMinRightMargin;break;}// 通知進度更新publishProgress(contentLayoutParamsRightMargin);// 線程睡眠15毫秒,便於體現滾動效果try {Thread.sleep(15);} catch (Exception e) {}}// 依據滑動的速度設定標誌位isMenuVisibleif (speed[0] > 0) {isMenuVisible = false;} else {isMenuVisible = true;}isSliding = false;return contentLayoutParamsRightMargin;}@Overrideprotected void onProgressUpdate(Integer... rightMargin) {super.onProgressUpdate(rightMargin);mContentLayoutParams.rightMargin = rightMargin[0];contentView.setLayoutParams(mContentLayoutParams);unFocusBindView();}@Overrideprotected void onPostExecute(Integer rightMargin) {super.onPostExecute(rightMargin);mContentLayoutParams.rightMargin = rightMargin;contentView.setLayoutParams(mContentLayoutParams);}}}
main.xml如下: