可动态布局的Android抽屉之完整篇

嵌入式操作系统

57人已加入

描述

  以前曾经介绍过《Android提高第十九篇之“多方向”抽屉》,当这个抽屉组件不与周围组件发生压挤的情况下(周围组件布局不变),是比较好使的,但是如果需要对周围组件挤压,则用起来欠缺美观了。

  如下图。在对周围压挤的情况下,抽屉是先把周围的组件一次性压挤,再通过动画效果展开/收缩的,这种做法的好处是快速简单,坏处是如果挤压范围过大,则效果生硬。

Android  

  本文实现的自定义抽屉组件,主要针对这种压挤效果做出改良,渐进式压挤周围组件,使得过渡效果更加美观。如下图。

  Android

  本文实现的抽屉原理是酱紫:

  1.抽屉组件主要在屏幕不可视区域,手柄在屏幕边缘的可视区域。即 抽屉.rightMargin=-XXX + 手柄.width

  2.指定一个周围组件为可压挤,即LayoutParams.weight=1;当然用户也可以指定多个View.

  3.使用AsyncTask来实现弹出/收缩的动画,弹出:抽屉.rightMargin+=XX,收缩:抽屉.rightMargin-=XX

  总结,本文的自定义抽屉虽然对压挤周围组件有过渡效果,但是比较耗资源,读者可以针对不同的情况考虑使用。

  本文的源码可以到http://download.csdn.net/detail/hellogv/3615686 下载。

  接下来贴出本文全部源代码:

  main.xml的源码:

  [html] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》《?xml version=“1.0” encoding=“utf-8”?》

  《LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  android:layout_width=“fill_parent” android:layout_height=“fill_parent”

  android:id=“@+id/container”》

  《GridView android:id=“@+id/gridview” android:layout_width=“fill_parent”

  android:layout_height=“fill_parent” android:numColumns=“auto_fit”

  android:verticalSpacing=“10dp” android:gravity=“center”

  android:columnWidth=“50dip” android:horizontalSpacing=“10dip” /》

  《/LinearLayout》《/span》

  《span style=“font-family:Comic Sans MS;font-size:18px;”》《?xml version=“1.0” encoding=“utf-8”?》

  《LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  android:layout_width=“fill_parent” android:layout_height=“fill_parent”

  android:id=“@+id/container”》

  《GridView android:id=“@+id/gridview” android:layout_width=“fill_parent”

  android:layout_height=“fill_parent” android:numColumns=“auto_fit”

  android:verticalSpacing=“10dp” android:gravity=“center”

  android:columnWidth=“50dip” android:horizontalSpacing=“10dip” /》

  《/LinearLayout》《/span》

  GridView的Item.xml的源码:

  [html] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》《?xml version=“1.0” encoding=“utf-8”?》

  《RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  android:layout_height=“wrap_content” android:paddingBottom=“4dip”

  android:layout_width=“fill_parent”》

  《ImageView android:layout_height=“wrap_content” android:id=“@+id/ItemImage”

  android:layout_width=“wrap_content” android:layout_centerHorizontal=“true”》

  《/ImageView》

  《TextView android:layout_width=“wrap_content”

  android:layout_below=“@+id/ItemImage” android:layout_height=“wrap_content”

  android:text=“TextView01” android:layout_centerHorizontal=“true”

  android:id=“@+id/ItemText”》

  《/TextView》

  《/RelativeLayout》 《/span》

  《span style=“font-family:Comic Sans MS;font-size:18px;”》《?xml version=“1.0” encoding=“utf-8”?》

  《RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”

  android:layout_height=“wrap_content” android:paddingBottom=“4dip”

  android:layout_width=“fill_parent”》

  《ImageView android:layout_height=“wrap_content” android:id=“@+id/ItemImage”

  android:layout_width=“wrap_content” android:layout_centerHorizontal=“true”》

  《/ImageView》

  《TextView android:layout_width=“wrap_content”

  android:layout_below=“@+id/ItemImage” android:layout_height=“wrap_content”

  android:text=“TextView01” android:layout_centerHorizontal=“true”

  android:id=“@+id/ItemText”》

  《/TextView》

  《/RelativeLayout》 《/span》

  Panel.java是本文核心,抽屉组件的源码,这个抽屉只实现了从右往左的弹出/从左往右的收缩,读者可以根据自己的需要修改源码来改变抽屉动作的方向:

  [java] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》public class Panel extends LinearLayout{

  public interface PanelClosedEvent {

  void onPanelClosed(View panel);

  }

  public interface PanelOpenedEvent {

  void onPanelOpened(View panel);

  }

  /**Handle的宽度,与Panel等高*/

  private final static int HANDLE_WIDTH=30;

  /**每次自动展开/收缩的范围*/

  private final static int MOVE_WIDTH=20;

  private Button btnHandle;

  private LinearLayout panelContainer;

  private int mRightMargin=0;

  private Context mContext;

  private PanelClosedEvent panelClosedEvent=null;

  private PanelOpenedEvent panelOpenedEvent=null;

  /**

  * otherView自动布局以适应Panel展开/收缩的空间变化

  * @author GV

  *

  */

  public Panel(Context context,View otherView,int width,int height) {

  super(context);

  this.mContext=context;

  //改变Panel附近组件的属性

  LayoutParams otherLP=(LayoutParams) otherView.getLayoutParams();

  otherLP.weight=1;//支持压挤

  otherView.setLayoutParams(otherLP);

  //设置Panel本身的属性

  LayoutParams lp=new LayoutParams(width, height);

  lp.rightMargin=-lp.width+HANDLE_WIDTH;//Panel的Container在屏幕不可视区域,Handle在可视区域

  mRightMargin=Math.abs(lp.rightMargin);

  this.setLayoutParams(lp);

  this.setOrientation(LinearLayout.HORIZONTAL);

  //设置Handle的属性

  btnHandle=new Button(context);

  btnHandle.setLayoutParams(new LayoutParams(HANDLE_WIDTH,height));

  btnHandle.setOnClickListener(new OnClickListener(){

  @Override

  public void onClick(View arg0) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 《 0)// CLOSE的状态

  new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正数展开

  else if (lp.rightMargin 》= 0)// OPEN的状态

  new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 负数收缩

  }

  });

  //btnHandle.setOnTouchListener(HandleTouchEvent);

  this.addView(btnHandle);

  //设置Container的属性

  panelContainer=new LinearLayout(context);

  panelContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,

  LayoutParams.FILL_PARENT));

  this.addView(panelContainer);

  }

  /**

  * 定义收缩时的回调函数

  * @param event

  */

  public void setPanelClosedEvent(PanelClosedEvent event)

  {

  this.panelClosedEvent=event;

  }

  /**

  * 定义展开时的回调函数

  * @param event

  */

  public void setPanelOpenedEvent(PanelOpenedEvent event)

  {

  this.panelOpenedEvent=event;

  }

  /**

  * 把View放在Panel的Container

  * @param v

  */

  public void fillPanelContainer(View v)

  {

  panelContainer.addView(v);

  }

  /**

  * 异步移动Panel

  * @author hellogv

  */

  class AsynMove extends AsyncTask《Integer, Integer, Void》 {

  @Override

  protected Void doInBackground(Integer.。. params) {

  int times;

  if (mRightMargin % Math.abs(params[0]) == 0)// 整除

  times = mRightMargin / Math.abs(params[0]);

  else

  // 有余数

  times = mRightMargin / Math.abs(params[0]) + 1;

  for (int i = 0; i 《 times; i++) {

  publishProgress(params);

  try {

  Thread.sleep(Math.abs(params[0]));

  } catch (InterruptedException e) {

  // TODO Auto-generated catch block

  e.printStackTrace();

  }

  }

  return null;

  }

  @Override

  protected void onProgressUpdate(Integer.。. params) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (params[0] 《 0)

  lp.rightMargin = Math.max(lp.rightMargin + params[0],

  (-mRightMargin));

  else

  lp.rightMargin = Math.min(lp.rightMargin + params[0], 0);

  if(lp.rightMargin==0 && panelOpenedEvent!=null){//展开之后

  panelOpenedEvent.onPanelOpened(Panel.this);//调用OPEN回调函数

  }

  else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收缩之后

  panelClosedEvent.onPanelClosed(Panel.this);//调用CLOSE回调函数

  }

  Panel.this.setLayoutParams(lp);

  }

  }

  }

  《/span》

  《span style=“font-family:Comic Sans MS;font-size:18px;”》public class Panel extends LinearLayout{

  public interface PanelClosedEvent {

  void onPanelClosed(View panel);

  }

  public interface PanelOpenedEvent {

  void onPanelOpened(View panel);

  }

  /**Handle的宽度,与Panel等高*/

  private final static int HANDLE_WIDTH=30;

  /**每次自动展开/收缩的范围*/

  private final static int MOVE_WIDTH=20;

  private Button btnHandle;

  private LinearLayout panelContainer;

  private int mRightMargin=0;

  private Context mContext;

  private PanelClosedEvent panelClosedEvent=null;

  private PanelOpenedEvent panelOpenedEvent=null;

  /**

  * otherView自动布局以适应Panel展开/收缩的空间变化

  * @author GV

  *

  */

  public Panel(Context context,View otherView,int width,int height) {

  super(context);

  this.mContext=context;

  //改变Panel附近组件的属性

  LayoutParams otherLP=(LayoutParams) otherView.getLayoutParams();

  otherLP.weight=1;//支持压挤

  otherView.setLayoutParams(otherLP);

  //设置Panel本身的属性

  LayoutParams lp=new LayoutParams(width, height);

  lp.rightMargin=-lp.width+HANDLE_WIDTH;//Panel的Container在屏幕不可视区域,Handle在可视区域

  mRightMargin=Math.abs(lp.rightMargin);

  this.setLayoutParams(lp);

  this.setOrientation(LinearLayout.HORIZONTAL);

  //设置Handle的属性

  btnHandle=new Button(context);

  btnHandle.setLayoutParams(new LayoutParams(HANDLE_WIDTH,height));

  btnHandle.setOnClickListener(new OnClickListener(){

  @Override

  public void onClick(View arg0) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 《 0)// CLOSE的状态

  new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正数展开

  else if (lp.rightMargin 》= 0)// OPEN的状态

  new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 负数收缩

  }

  });

  //btnHandle.setOnTouchListener(HandleTouchEvent);

  this.addView(btnHandle);

  //设置Container的属性

  panelContainer=new LinearLayout(context);

  panelContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,

  LayoutParams.FILL_PARENT));

  this.addView(panelContainer);

  }

  /**

  * 定义收缩时的回调函数

  * @param event

  */

  public void setPanelClosedEvent(PanelClosedEvent event)

  {

  this.panelClosedEvent=event;

  }

  /**

  * 定义展开时的回调函数

  * @param event

  */

  public void setPanelOpenedEvent(PanelOpenedEvent event)

  {

  this.panelOpenedEvent=event;

  }

  /**

  * 把View放在Panel的Container

  * @param v

  */

  public void fillPanelContainer(View v)

  {

  panelContainer.addView(v);

  }

  /**

  * 异步移动Panel

  * @author hellogv

  */

  class AsynMove extends AsyncTask《Integer, Integer, Void》 {

  @Override

  protected Void doInBackground(Integer.。. params) {

  int times;

  if (mRightMargin % Math.abs(params[0]) == 0)// 整除

  times = mRightMargin / Math.abs(params[0]);

  else

  // 有余数

  times = mRightMargin / Math.abs(params[0]) + 1;

  for (int i = 0; i 《 times; i++) {

  publishProgress(params);

  try {

  Thread.sleep(Math.abs(params[0]));

  } catch (InterruptedException e) {

  // TODO Auto-generated catch block

  e.printStackTrace();

  }

  }

  return null;

  }

  @Override

  protected void onProgressUpdate(Integer.。. params) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (params[0] 《 0)

  lp.rightMargin = Math.max(lp.rightMargin + params[0],

  (-mRightMargin));

  else

  lp.rightMargin = Math.min(lp.rightMargin + params[0], 0);

  if(lp.rightMargin==0 && panelOpenedEvent!=null){//展开之后

  panelOpenedEvent.onPanelOpened(Panel.this);//调用OPEN回调函数

  }

  else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收缩之后

  panelClosedEvent.onPanelClosed(Panel.this);//调用CLOSE回调函数

  }

  Panel.this.setLayoutParams(lp);

  }

  }

  }

  《/span》

  main.java是主控部分,演示了Panel的使用:

  [java] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》public class main extends Activity {

  public Panel panel;

  public LinearLayout container;

  public GridView gridview;

  public void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);

  setContentView(R.layout.main);

  this.setTitle(““可动态布局”的抽屉组件之构建基础-----hellogv”);

  gridview = (GridView) findViewById(R.id.gridview);

  container=(LinearLayout)findViewById(R.id.container);

  panel=new Panel(this,gridview,200,LayoutParams.FILL_PARENT);

  container.addView(panel);//加入Panel控件

  //新建测试组件

  TextView tvTest=new TextView(this);

  tvTest.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));

  tvTest.setText(“测试组件,红字白底”);

  tvTest.setTextColor(Color.RED);

  tvTest.setBackgroundColor(Color.WHITE);

  //加入到Panel里面

  panel.fillPanelContainer(tvTest);

  panel.setPanelClosedEvent(panelClosedEvent);

  panel.setPanelOpenedEvent(panelOpenedEvent);

  //往GridView填充测试数据

  ArrayList《HashMap《String, Object》》 lstImageItem = new ArrayList《HashMap《String, Object》》();

  for (int i = 0; i 《 100; i++) {

  HashMap《String, Object》 map = new HashMap《String, Object》();

  map.put(“ItemImage”, R.drawable.icon);

  map.put(“ItemText”, “NO.” + String.valueOf(i));

  lstImageItem.add(map);

  }

  SimpleAdapter saImageItems = new SimpleAdapter(this,

  lstImageItem,

  R.layout.item,

  new String[] { “ItemImage”, “ItemText” },

  new int[] { R.id.ItemImage, R.id.ItemText });

  gridview.setAdapter(saImageItems);

  gridview.setOnItemClickListener(new ItemClickListener());

  }

  PanelClosedEvent panelClosedEvent =new PanelClosedEvent(){

  @Override

  public void onPanelClosed(View panel) {

  Log.e(“panelClosedEvent”,“panelClosedEvent”);

  }

  };

  PanelOpenedEvent panelOpenedEvent =new PanelOpenedEvent(){

  @Override

  public void onPanelOpened(View panel) {

  Log.e(“panelOpenedEvent”,“panelOpenedEvent”);

  }

  };

  class ItemClickListener implements OnItemClickListener {

  @Override

  public void onItemClick(AdapterView《?》 arg0,View arg1, int arg2, long arg3) {

  @SuppressWarnings(“unchecked”)

  HashMap《String, Object》 item = (HashMap《String, Object》) arg0

  .getItemAtPosition(arg2);

  setTitle((String) item.get(“ItemText”));

  }

  }《/span》

  这次就在基础篇的基础上加入拖拉功能。拖拉功能基于GestureDetector,GestureDetector的基本使用方式不是本文介绍的重点,有兴趣的童鞋可以上网查询相关的教程。

  本文的抽屉控件相对于基础篇的抽屉控件多了以下功能:

  1.支持手势拖拉

  2.拖拉到一半时,可以自动展开或者收缩。

  具体如下图:

  Android

  本文的源码可以到这里下载:http://download.csdn.net/detail/hellogv/3642418

  只贴出抽屉组件的源码,其他源文件与基础篇的一样:

  [java] view plaincopyprint?

  《span style=“font-family:Comic Sans MS;font-size:18px;”》public class Panel extends LinearLayout implements GestureDetector.OnGestureListener{

  public interface PanelClosedEvent {

  void onPanelClosed(View panel);

  }

  public interface PanelOpenedEvent {

  void onPanelOpened(View panel);

  }

  private final static int HANDLE_WIDTH=30;

  private final static int MOVE_WIDTH=20;

  private Button btnHandler;

  private LinearLayout panelContainer;

  private int mRightMargin=0;

  private Context mContext;

  private GestureDetector mGestureDetector;

  private boolean mIsScrolling=false;

  private float mScrollX;

  private PanelClosedEvent panelClosedEvent=null;

  private PanelOpenedEvent panelOpenedEvent=null;

  public Panel(Context context,View otherView,int width,int height) {

  super(context);

  this.mContext=context;

  //定义手势识别

  mGestureDetector = new GestureDetector(mContext,this);

  mGestureDetector.setIsLongpressEnabled(false);

  //改变Panel附近组件的属性

  LayoutParams otherLP=(LayoutParams) otherView.getLayoutParams();

  otherLP.weight=1;

  otherView.setLayoutParams(otherLP);

  //设置Panel本身的属性

  LayoutParams lp=new LayoutParams(width, height);

  lp.rightMargin=-lp.width+HANDLE_WIDTH;

  mRightMargin=Math.abs(lp.rightMargin);

  this.setLayoutParams(lp);

  this.setOrientation(LinearLayout.HORIZONTAL);

  //设置Handler的属性

  btnHandler=new Button(context);

  btnHandler.setLayoutParams(new LayoutParams(HANDLE_WIDTH,height));

  //btnHandler.setOnClickListener(handlerClickEvent);

  btnHandler.setOnTouchListener(handlerTouchEvent);

  this.addView(btnHandler);

  //设置Container的属性

  panelContainer=new LinearLayout(context);

  panelContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,

  LayoutParams.FILL_PARENT));

  this.addView(panelContainer);

  }

  private View.OnTouchListener handlerTouchEvent=new View.OnTouchListener() {

  @Override

  public boolean onTouch(View v, MotionEvent event) {

  if(event.getAction()==MotionEvent.ACTION_UP && //onScroll时的ACTION_UP

  mIsScrolling==true)

  {

  LayoutParams lp=(LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 》= (-mRightMargin/2)) {//往左超过一半

  new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正数展开

  }

  else if (lp.rightMargin 《 (-mRightMargin/2)) {//往右拖拉

  new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 负数收缩

  }

  }

  return mGestureDetector.onTouchEvent(event);

  }

  };

  /**

  * 定义收缩时的回调函数

  * @param event

  */

  public void setPanelClosedEvent(PanelClosedEvent event)

  {

  this.panelClosedEvent=event;

  }

  /**

  * 定义展开时的回调函数

  * @param event

  */

  public void setPanelOpenedEvent(PanelOpenedEvent event)

  {

  this.panelOpenedEvent=event;

  }

  /**

  * 把View放在Panel的Container

  * @param v

  */

  public void fillPanelContainer(View v)

  {

  panelContainer.addView(v);

  }

  /**

  * 异步移动Panel

  * @author hellogv

  */

  class AsynMove extends AsyncTask《Integer, Integer, Void》 {

  @Override

  protected Void doInBackground(Integer.。。 params) {

  int times;

  if (mRightMargin % Math.abs(params[0]) == 0)// 整除

  times = mRightMargin / Math.abs(params[0]);

  else

  // 有余数

  times = mRightMargin / Math.abs(params[0]) + 1;

  for (int i = 0; i 《 times; i++) {

  publishProgress(params);

  try {

  Thread.sleep(Math.abs(params[0]));

  } catch (InterruptedException e) {

  // TODO Auto-generated catch block

  e.printStackTrace();

  }

  }

  return null;

  }

  @Override

  protected void onProgressUpdate(Integer.。。 params) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (params[0] 《 0)

  lp.rightMargin = Math.max(lp.rightMargin + params[0],

  (-mRightMargin));

  else

  lp.rightMargin = Math.min(lp.rightMargin + params[0], 0);

  if(lp.rightMargin==0 && panelOpenedEvent!=null){//展开之后

  panelOpenedEvent.onPanelOpened(Panel.this);//调用OPEN回调函数

  }

  else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收缩之后

  panelClosedEvent.onPanelClosed(Panel.this);//调用CLOSE回调函数

  }

  Panel.this.setLayoutParams(lp);

  }

  }

  @Override

  public boolean onDown(MotionEvent e) {

  mScrollX=0;

  mIsScrolling=false;

  return false;

  }

  @Override

  public boolean onSingleTapUp(MotionEvent e) {

  LayoutParams lp = (LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 《 0)// CLOSE的状态

  new AsynMove().execute(new Integer[] { MOVE_WIDTH });// 正数展开

  else if (lp.rightMargin 》= 0)// OPEN的状态

  new AsynMove().execute(new Integer[] { -MOVE_WIDTH });// 负数收缩

  return false;

  }

  @Override

  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,

  float distanceY) {

  mIsScrolling=true;

  mScrollX+=distanceX;

  LayoutParams lp=(LayoutParams) Panel.this.getLayoutParams();

  if (lp.rightMargin 《 -1 && mScrollX 》 0) {//往左拖拉

  lp.rightMargin = Math.min((lp.rightMargin + (int) mScrollX),0);

  Panel.this.setLayoutParams(lp);

  Log.e(“onScroll”,lp.rightMargin+“”);

  }

  else if (lp.rightMargin 》 -(mRightMargin) && mScrollX 《 0) {//往右拖拉

  lp.rightMargin = Math.max((lp.rightMargin + (int) mScrollX),-mRightMargin);

  Panel.this.setLayoutParams(lp);

  }

  if(lp.rightMargin==0 && panelOpenedEvent!=null){//展开之后

  panelOpenedEvent.onPanelOpened(Panel.this);//调用OPEN回调函数

  }

  else if(lp.rightMargin==-(mRightMargin) && panelClosedEvent!=null){//收缩之后

  panelClosedEvent.onPanelClosed(Panel.this);//调用CLOSE回调函数

  }

  Log.e(“onScroll”,lp.rightMargin+“”);

  return false;

  }

  @Override

  public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,

  float velocityY) {return false;}

  @Override

  public void onLongPress(MotionEvent e) {}

  @Override

  public void onShowPress(MotionEvent e) {}

  }

  《/span》

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分