Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android側滑菜單完整詳細示例(改進版)

Android側滑菜單完整詳細示例(改進版)

編輯:關於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;
	@Override
	protected 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() {
			@Override
			public 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() {
			@Override
			public 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;
	// TAG
	private final static String TAG = "SlidingMenuRelativeLayout";
	// menu的布局LayoutParams
	private MarginLayoutParams mMenuLayoutParams;
	// content的布局LayoutParams
	private 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);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		if (changed) {
			// 初始化contentView
			contentView = getChildAt(1);
			mContentLayoutParams = (MarginLayoutParams) contentView.getLayoutParams();
			// 將contentView的寬度設置為屏幕的寬度
			mContentLayoutParams.width = screenWidth;
			contentView.setLayoutParams(mContentLayoutParams);

			// 初始化menuView
			menuView = getChildAt(0);
			mMenuLayoutParams = (MarginLayoutParams) menuView.getLayoutParams();
			// 設置contentView的最小和最大rightMargin值.
			contentParamsMinRightMargin = -mMenuLayoutParams.width;
			contentParamsMaxRightMargin = 0;

			mButton = (Button) menuView.findViewById(R.id.menuButton);
			mButton.setOnClickListener(new OnClickListener() {
				@Override
				public 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();
		// 獲取ScaledTouchSlop
		scaledTouchSlop = 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 {
		@Override
		public 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的LayoutParams
					contentView.setLayoutParams(mContentLayoutParams);
				}

				// 手指滑向屏幕左側,distanceX為負數
				if (isMenuVisible && -distanceX >= scaledTouchSlop) {
					isSliding = true;
					mContentLayoutParams.rightMargin = contentParamsMinRightMargin - distanceX;
					// 處理越界的情況
					if (mContentLayoutParams.rightMargin > contentParamsMaxRightMargin) {
						mContentLayoutParams.rightMargin = contentParamsMaxRightMargin;
					}
					// 設置contentView的LayoutParams
					contentView.setLayoutParams(mContentLayoutParams);
				}
				break;
			case MotionEvent.ACTION_UP:
				xUp = event.getRawX();
				int upDistanceX = (int) (xUp - xDown);
				if (isSliding) {
					// 判斷手勢意圖想顯示menu
					if (wantToShowMenu()) {
						// 判斷是否顯示menu
						if (shouldScrollToMenu()) {
							scrollToMenu();
						} else {
							scrollToContent();
						}
					}

					// 判斷手勢意圖想顯示content
					if (wantToShowContent()) {
						// 判斷是否顯示content
						if (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 {
		@Override
		protected Integer doInBackground(Integer... speed) {
			int contentLayoutParamsRightMargin = mContentLayoutParams.rightMargin;
			while (true) {
				// 每次變化的speed
				contentLayoutParamsRightMargin = 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) {
				}
			}

			// 依據滑動的速度設置標志位isMenuVisible
			if (speed[0] > 0) {
				isMenuVisible = false;
			} else {
				isMenuVisible = true;
			}
			isSliding = false;
			return contentLayoutParamsRightMargin;
		}

		@Override
		protected void onProgressUpdate(Integer... rightMargin) {
			super.onProgressUpdate(rightMargin);
			mContentLayoutParams.rightMargin = rightMargin[0];
			contentView.setLayoutParams(mContentLayoutParams);
			unFocusBindView();
		}

		@Override
		protected void onPostExecute(Integer rightMargin) {
			super.onPostExecute(rightMargin);
			mContentLayoutParams.rightMargin = rightMargin;
			contentView.setLayoutParams(mContentLayoutParams);
		}
	}

}

main.xml如下:



    

        
        


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved