SlidingDrawer这个控件顾名思义就像抽屉一样,抽屉里面的东西(content)是对外面隐藏的,只有抽屉的把手(handle)是暴露在外面的。当用户触动把手时,抽屉内隐藏的内容就会呈现。 配置上采用了水平展开或垂直展开两种(android:orientation)方式,在XML里必须指定其使用的android:handle 与android:content,前者委托要展开的图片(Layout 配置),后者则是要展开的Layout Content。用抽屉的外部布局一般采用相对布局(RelativeLayout)或是帧布局(FrameLayout),之样才只可以让抽屉的行为控制在指定的范围内。由于抽屉的高度在XML中指定为wrap_content是没有效果的,所以程序最好可以动态的根据抽屉的打开或是关闭来设置布局的显示位置。在主程序中,可以通过为SlidingDrawer设置监听器捕捉"打开完毕"与"已经关闭"这两个事件,所设置的Listener 为SlidingDrawer.setOnDrawerOpenListener()与SlidingDrawer.setOnDrawerCloseListener()。可以在这两个方法里面动态的改变布局。
下面就以相对布局为例 里面有一个Button,ListView和一个自定义的SlidingDrawer它的把手部分的左右有两个TextView。抽屉里面是一个简单的TextView控件。main.xml布局如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/btOpen"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="#0ff"
android:text="Opent handle" />
<ListView
android:layout_below="@+id/btOpen"
android:id="@+id/myListView"
android:layout_width="fill_parent"
android:layout_height="400dip"
android:background="#fff" />
<com.renzh.slidingdrawertest.MySlidingDrawer
android:id="@+id/ctlSlidingDrawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:content="@+id/content"
android:handle="@+id/handle" >
<LinearLayout
android:id="@+id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/btnLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_launcher"
android:text="Left"
android:textColor="#f00"
android:textSize="16pt" >
</TextView>
<Button
android:id="@+id/ctlHandle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Handle" >
</Button>
<TextView
android:id="@+id/btnRight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_launcher"
android:text="Right"
android:textColor="#f00"
android:textSize="16pt" >
</TextView>
</LinearLayout>
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/textView1"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="#f00"
android:textSize="18pt"
android:text="This is the content" >
</TextView>
</LinearLayout>
</com.renzh.slidingdrawertest.MySlidingDrawer>
</RelativeLayout>
以上具有ID为ctHandle的控件将作为我们指定的唯一把手,而它两边的控件虽然在把手handle的布局内,它们将没有把手的功能,但是有自己的响应事件而不被把手影响的独立性,这个效果主要是靠重写SlidingDrawer来处理触摸事件,并去检测我们指定的把手和指定把手布局内的其它活动控件。并拦截和处理,分发消息给子控件。具体见代码。
布局效果如下图示:
接下来就要去实现自定义的抽屉了,因为这个把手需要我们自定义去处理,为了让把手周边的控件可以响应操作。那么自定义的抽屉类如下:
package com.renzh.slidingdrawertest;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.SlidingDrawer;
public class MySlidingDrawer extends SlidingDrawer {
private int mHandleId = 0; // 抽屉行为控件ID
private int[] mTouchableIds = null; // Handle 部分其他控件ID
public int[] getTouchableIds() {
return mTouchableIds;
}
public void setTouchableIds(int[] mTouchableIds) {
this.mTouchableIds = mTouchableIds;
}
public int getHandleId() {
return mHandleId;
}
public void setHandleId(int mHandleId) {
this.mHandleId = mHandleId;
}
public MySlidingDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MySlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/* * 获取控件的屏幕区域 */
public Rect getRectOnScreen(View view) {
Rect rect = new Rect();
int[] location = new int[2];
View parent = view;
if (view.getParent() instanceof View) {
parent = (View) view.getParent();
}
parent.getLocationOnScreen(location);
view.getHitRect(rect);
rect.offset(location[0], location[1]);
return rect;
}
public boolean onInterceptTouchEvent(MotionEvent event) {
// 触摸位置转换为屏幕坐标
int[] location = new int[2];
int x = (int) event.getX();
int y = (int) event.getY();
this.getLocationOnScreen(location);
x += location[0];
y += location[1];
// handle部分独立按钮
if (mTouchableIds != null) {
for (int id : mTouchableIds) {
View view = findViewById(id);
Rect rect = getRectOnScreen(view);
if (rect.contains(x, y)) {
//在这里可以定制自己的消息。
if(MotionEvent.ACTION_DOWN==event.getAction()){
view.dispatchTouchEvent(event);
}
return false;
}
}
}
// 抽屉行为控件
if (event.getAction() == MotionEvent.ACTION_DOWN && mHandleId != 0) {
View view = findViewById(mHandleId);
Log.i("MySlidingDrawer on touch", String.format("%d,%d", x, y));
Rect rect = getRectOnScreen(view);
Log.i("MySlidingDrawer handle screen rect", String
.format("%d,%d %d,%d", rect.left, rect.top, rect.right,
rect.bottom));
if (rect.contains(x, y)) {
// 点击抽屉控件时交由系统处理
Log.i("MySlidingDrawer", "Hit handle");
} else {
return false;
}
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("MySlidingDrawer on touch", "Action=" + event.getAction());
return super.onTouchEvent(event);
}
/*
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.UNSPECIFIED
|| heightSpecMode == MeasureSpec.UNSPECIFIED) {
throw new RuntimeException(
"SlidingDrawer cannot have UNSPECIFIED dimensions");
}
final View handle = getHandle();
final View content = getContent();
measureChild(handle, widthMeasureSpec, heightMeasureSpec);
int extra = handle.getHeight() / 6;
System.out.println(handle.getMeasuredHeight() + " "
+ content.getHeight());
if (mVertical) {
int height = heightSpecSize - handle.getMeasuredHeight()
- mTopOffset;
content.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(height, heightSpecMode));
heightSpecSize = handle.getMeasuredHeight()
+ mTopOffset + content.getMeasuredHeight();
widthSpecSize = content.getMeasuredWidth();
if (handle.getMeasuredWidth() > widthSpecSize)
widthSpecSize = handle.getMeasuredWidth();
}
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
private boolean mVertical;
private int mTopOffset;
*/
}
package com.renzh.slidingdrawertest;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.SlidingDrawer.OnDrawerCloseListener;
import android.widget.SlidingDrawer.OnDrawerOpenListener;
import android.widget.TextView;
import android.widget.Toast;
public class SlidingDrawerTestActivity extends Activity {
TextView btnLeft;
TextView btnRight ;
Button btOpne=null;
MySlidingDrawer ctlSlidingDrawer;
ListView myList=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.main);
ctlSlidingDrawer = (MySlidingDrawer) findViewById(R.id.ctlSlidingDrawer);
ctlSlidingDrawer.setHandleId(R.id.ctlHandle);
ctlSlidingDrawer.setTouchableIds(new int[] { R.id.btnLeft,
R.id.btnRight });
ctlSlidingDrawer.setOnDrawerOpenListener(new OnDrawerOpenListener(){
public void onDrawerOpened() {
RelativeLayout.LayoutParams lps=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT);
lps.addRule(RelativeLayout.ABOVE, R.id.ctlSlidingDrawer);
lps.addRule(RelativeLayout.BELOW, R.id.btOpen);
myList.setLayoutParams(lps);
myList.setBackgroundColor(Color.BLUE);
}
});
ctlSlidingDrawer.setOnDrawerCloseListener(new OnDrawerCloseListener(){
public void onDrawerClosed() {
myList.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,600));
RelativeLayout.LayoutParams lps=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT);
lps.setMargins(0, 0, 0, ctlSlidingDrawer.getHandle().getHeight());
lps.addRule(RelativeLayout.BELOW, R.id.btOpen);
myList.setLayoutParams(lps);
myList.setBackgroundColor(Color.GREEN);
}
});
RelativeLayout.LayoutParams lps=new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,250);
lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
ctlSlidingDrawer.setLayoutParams(lps);
btnLeft = (TextView) this.findViewById(R.id.btnLeft);
btnLeft.setOnClickListener(onLeftClickListener);
btnRight = (TextView) this.findViewById(R.id.btnRight);
btnRight.setOnClickListener(onRightClickListener);
TextView tv=(TextView) findViewById(R.id.textView1);
tv.setBackgroundResource(R.drawable.list_divider);
tv.setOnClickListener(onLeftClickListener);
myList =(ListView) findViewById(R.id.myListView);;
btOpne=(Button) findViewById(R.id.btOpen);
btOpne.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
ctlSlidingDrawer.toggle();
}
});
}
private OnClickListener onLeftClickListener = new OnClickListener() {
public void onClick(View v) {
btnLeft.setBackgroundResource(R.drawable.mypos);
Toast.makeText(SlidingDrawerTestActivity.this, "left", Toast.LENGTH_SHORT).show();
}
};
private OnClickListener onRightClickListener = new OnClickListener() {
public void onClick(View v) {
btnRight.setBackgroundResource(R.drawable.mypos);
Toast.makeText(SlidingDrawerTestActivity.this, "right", Toast.LENGTH_SHORT).show();
}
};
}
当打开抽屉的时候界面如下:
当然这里的有效把手是Handle那个button控件,可以拖拉它来打开或是关闭,同时把手左右两边的两个控件分别是TextView,点击这两个控件会有响应,同时会换张图片。以上的代码关键在重写SlidingDrawer控件,通过拦截触摸事件,遍历把手部分的所有控件,只让我们在自定义控件中指定的某个控件Id具有把手的功能,其它的控件没有把手功能,并把触摸消息分发到没有把手功能的子控件上,而不会被SlidingDrawer拦截掉。其次在SlidingDrawer控件打开和关闭时要动态的去设置相对布局的位置信息,这样就可以随意控件SlidingDrawer的显示范围和位置了。
以上布局不太美观,但是它很简明的测试了自定义抽屉的功能完整性,达到了很多朋友想要的效果,这样抽屉外面的把手不再单调的只显示把手了,也可以显示用户指定的内容了,并且它们都有自己的焦点,不会影响指定把手的功能和焦点或是受原抽屉控件的限制。 现在美丽的布局由你定,动手吧。