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的显示范围和位置了。
以上布局不太美观,但是它很简明的测试了自定义抽屉的功能完整性,达到了很多朋友想要的效果,这样抽屉外面的把手不再单调的只显示把手了,也可以显示用户指定的内容了,并且它们都有自己的焦点,不会影响指定把手的功能和焦点或是受原抽屉控件的限制。 现在美丽的布局由你定,动手吧。
想无时无刻都在推广您的应用?把您的签名档换成您的应用名片吧!将带连接的图片放在邮件中,随时随地的传播您的应用。
比如应用汇的签名档
如果您是博主,在谈到某个应用的时候是不是总要费力找图标,找截图加链接。现在,应用汇推出的app widget为您节省下这个时间。只需将应用的包名填写在下面的输入框内,将生成的代码粘贴到编辑器的HTML模式中就可以啦!
比如应用汇的App widget!
来试试吧
应用汇实验室
http://blog.csdn.net/lovehong0306
在用TabWidget的时候会惊奇的发现,那个Tab选项卡竟然不能设置字体大小,而默认的字体竟然是那么的小,很是蛋疼。
怎么办?有两种办法。
第一中就是自定义了
TabHost.TabSpec有个setIndicator(view)方法
将自己写好的View传进去,想要什么样的自己把握,比较灵活。
但是像我这种要求没那么高,就想把字体改大点、设为居中显示就能满足需求的,人又比较懒,不想自定义,怎么办?
前边已经说了有两种办法,第二种办法就是为了满足懒人的需求。
既然GG没有提供相应的方法来实现这些功能,那就干脆直接取得控件(无非就是一个TextView),来个釜底抽薪,想怎么办全由着你了。
for (int i = 0; i < tabHost.getTabWidget().getChildCount(); i++) { TextView textView = (TextView)tabHost.getTabWidget().getChildAt(i).findViewById(android.R.id.title); textView.setTextSize(30); textView.setGravity(Gravity.CENTER); textView.getLayoutParams().height = LayoutParams.MATCH_PARENT; textView.getLayoutParams().width = LayoutParams.MATCH_PARENT;
// textView.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); 不推荐此种方法 }
此种方法来源于百度,至于是哪位大神发现的就不得而知了。
需要注意的是设置LayoutParams的时候,建议用上边那种。
原因是直接set需要知道该view的父view的类型,然后传入相应类型的LayoutParams,否则会抛出转型异常。
OK,懒人万岁!
http://blog.csdn.net/lovehong0306