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<String> 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<String>(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<Integer, Integer, Integer> {
@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如下:
<cc.cd.SlidingMenuRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/slidingMenuRelativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<RelativeLayout
android:id="@+id/menuRelativeLayout"
android:layout_width="270dip"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:background="#00ccff">
<TextView
android:id="@+id/menuTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="This is Menu"
android:textColor="#000000"
android:textSize="25sp" />
<Button
android:id="@+id/menuButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Click here"
android:textColor="#000000"
android:textSize="25sp"
/>
</RelativeLayout>
<LinearLayout
android:id="@+id/contentLinearLayout"
android:layout_width="320dip"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:background="#e9e9e9"
android:orientation="vertical" >
<Button
android:id="@+id/contentMenuButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Menu" />
<ListView
android:id="@+id/contentListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" />
</LinearLayout>
</cc.cd.SlidingMenuRelativeLayout>