Android自定义控件仿QQ抽屉效果

其实网上类似的实现已经很多了,原理也并不难,只是网上各种demo运行下来,多少都有一些问题。折腾了半天,决定自己实现一个。

首先我们看看实现效果:

对比网上各类demo,这次要实现的主要表现在以下几点:

1.侧滑显示抽屉view
2.侧滑抽屉隐藏view控件点击事件
3.单击任意item隐藏显示的抽屉view
4.滑动list隐藏显示的抽屉view
5.增加SwipeLayout点击事件和Swipe touch事件判断处理
6.优化快速划开多个抽屉隐藏view时多个SwipeLayout滑动状态判断处理,仅显示最后一个滑动的抽屉隐藏view,隐藏前面所有打开的抽屉view(快速滑动时,可能存在多个抽屉view打开情况,网上找的几个demo主要问题都集中在这一块)

实现原理

其实单就一个SwipeLayout的实现原理来讲的话,还是很简单的,实际上单个SwipeLayout隐藏抽屉状态时,应该是这样的:

也就是说,最初的隐藏状态,实际上是将hide view区域layout到conten view的右边,达到隐藏效果,而后显示则是根据拖拽的x值变化来动态的layout 2个view,从而达到一个滑动抽屉效果。
当然,直接重写view的onTouchEvent来动态的layout 2个view是可以实现我们需要的效果的,但是有更好的方法来实现,就是同过ViewDragHelper。
ViewDragHelper是google官方提供的一个专门用于手势分析处理的类,关于ViewDragHelper的基本使用,网上有一大堆的资源。具体的ViewDragHelper介绍以及基本使用方法,本文就不重复造轮子了,此处推荐鸿洋大神的一篇微博:Android ViewDragHelper完全解析 自定义ViewGroup神器。

具体实现

下面我们开始具体的实现。
布局比较简单,这里就不贴代码了,最后会贴上本demo的完整代码地址。

首先我们实现一个继承FrameLayout的自定义SwipeLauout,重写onFinishInflate方法:
这里我们只允许SwipeLayout设置2个子View,ContentLayout是继承LinearLayout的自定义layout,后面会讲到这个,此处先略过;

 @Override
 protected void onFinishInflate() {
  super.onFinishInflate();
  if (getChildCount() != 2) {
   throw new IllegalStateException("Must 2 views in SwipeLayout");
  }
  contentView = getChildAt(0);
  hideView = getChildAt(1);
  if (contentView instanceof ContentLayout)
   ((ContentLayout) contentView).setSwipeLayout(this);
  else {
   throw new IllegalStateException("content view must be an instanceof FrontLayout");
  }
 }

接着重写onSizeChanged,onLayout,onInterceptTouchEvent方法:

 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(w, h, oldw, oldh);
  hideViewHeight = hideView.getMeasuredHeight();
  hideViewWidth = hideView.getMeasuredWidth();
  contentWidth = contentView.getMeasuredWidth();
 }

 @Override
 protected void onLayout(boolean changed, int left, int top, int right,
       int bottom) {
  // super.onLayout(changed, left, top, right, bottom);
  contentView.layout(0, 0, contentWidth, hideViewHeight);
  hideView.layout(contentView.getRight(), 0, contentView.getRight()
    + hideViewWidth, hideViewHeight);
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  boolean result = viewDragHelper.shouldInterceptTouchEvent(ev);
  //  Log.e("SwipeLayout", "-----onInterceptTouchEvent-----");
  return result;
 }

然后是比较关键的,重写onTouchEvent方法以及ViewDragHelper.Callback回调,我们定了一个enum来判断SwipeLayout的三种状态。在onViewPositionChanged中,有2种方法实现content view和hide view的伴随移动,一种是直接offset view的横向变化量,还有一种就是直接通过layout的方式,两种方式都可以。

 public enum SwipeState {
  Open, Swiping, Close;
 }
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  //  Log.e("SwipeLayout", "-----onTouchEvent-----");
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN:
    downX = event.getX();
    downY = event.getY();
    break;
   case MotionEvent.ACTION_MOVE:
    // 1.获取x和y方向移动的距离
    float moveX = event.getX();
    float moveY = event.getY();
    float delatX = moveX - downX;// x方向移动的距离
    float delatY = moveY - downY;// y方向移动的距离

    if (Math.abs(delatX) > Math.abs(delatY)) {
     // 表示移动是偏向于水平方向,那么应该SwipeLayout应该处理,请求listview不要拦截
     this.requestDisallowInterceptTouchEvent(true);
    }

    // 更新downX,downY
    downX = moveX;
    downY = moveY;
    break;
   case MotionEvent.ACTION_UP:

    break;
  }
  viewDragHelper.processTouchEvent(event);
  return true;
 }

 private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
  @Override
  public boolean tryCaptureView(View child, int pointerId) {
   return child == contentView || child == hideView;
  }

  @Override
  public int getViewHorizontalDragRange(View child) {
   return hideViewWidth;
  }

  @Override
  public int clampViewPositionHorizontal(View child, int left, int dx) {
   if (child == contentView) {
    if (left > 0)
     left = 0;
    if (left < -hideViewWidth)
     left = -hideViewWidth;
   } else if (child == hideView) {
    if (left > contentWidth)
     left = contentWidth;
    if (left < (contentWidth - hideViewWidth))
     left = contentWidth - hideViewWidth;
   }
   return left;
  }

  @Override
  public void onViewPositionChanged(View changedView, int left, int top,
           int dx, int dy) {
   super.onViewPositionChanged(changedView, left, top, dx, dy);
   if (changedView == contentView) {
    // 如果手指滑动deleteView,那么也要讲横向变化量dx设置给contentView
    hideView.offsetLeftAndRight(dx);
   } else if (changedView == hideView) {
    // 如果手指滑动contentView,那么也要讲横向变化量dx设置给deleteView
    contentView.offsetLeftAndRight(dx);
   }

   //   if (changedView == contentView) {
   //    // 手动移动deleteView
   //    hideView.layout(hideView.getLeft() + dx,
   //      hideView.getTop() + dy, hideView.getRight() + dx,
   //      hideView.getBottom() + dy);
   //   } else if (hideView == changedView) {
   //    // 手动移动contentView
   //    contentView.layout(contentView.getLeft() + dx,
   //      contentView.getTop() + dy, contentView.getRight() + dx,
   //      contentView.getBottom() + dy);
   //   }
   //实时更新当前状态
   updateSwipeStates();
   invalidate();
  }

  @Override
  public void onViewReleased(View releasedChild, float xvel, float yvel) {
   super.onViewReleased(releasedChild, xvel, yvel);
   //根据用户滑动速度处理开关
   //xvel: x方向滑动速度
   //yvel: y方向滑动速度
   //   Log.e("tag", "currentState = " + currentState);
   //   Log.e("tag", "xvel = " + xvel);
   if (xvel < -200 && currentState != SwipeState.Open) {
    open();
    return;
   } else if (xvel > 200 && currentState != SwipeState.Close) {
    close();
    return;
   }

   if (contentView.getLeft() < -hideViewWidth / 2) {
    // 打开
    open();
   } else {
    // 关闭
    close();
   }
  }
 };

open(),close()实现

 public void open() {
  open(true);
 }

 public void close() {
  close(true);
 }

 /**
  * 打开的方法
  *
  * @param isSmooth 是否通过缓冲动画的形式设定view的位置
  */
 public void open(boolean isSmooth) {
  if (isSmooth) {
   viewDragHelper.smoothSlideViewTo(contentView, -hideViewWidth,
     contentView.getTop());
   ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
  } else {
   contentView.offsetLeftAndRight(-hideViewWidth);//直接偏移View的位置
   hideView.offsetLeftAndRight(-hideViewWidth);//直接偏移View的位置
   //   contentView.layout(-hideViewWidth, 0, contentWidth - hideViewWidth, hideViewHeight);//直接通过坐标摆放
   //   hideView.layout(contentView.getRight(), 0, hideViewWidth, hideViewHeight);//直接通过坐标摆放
   invalidate();
  }
 }

 /**
  * 关闭的方法
  *
  * @param isSmooth true:通过缓冲动画的形式设定view的位置
  *     false:直接设定view的位置
  */
 public void close(boolean isSmooth) {
  if (isSmooth) {
   viewDragHelper.smoothSlideViewTo(contentView, 0, contentView.getTop());
   ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
  } else {
   contentView.offsetLeftAndRight(hideViewWidth);
   hideView.offsetLeftAndRight(hideViewWidth);
   invalidate();
   //contentView.layout(0, 0, contentWidth, hideViewHeight);//直接通过坐标摆放
   //hideView.layout(contentView.getRight(), 0, hideViewWidth, hideViewHeight);//直接通过坐标摆放
  }
 }

此上基本实现了单个SwipeLayout的抽屉滑动效果,但是将此SwipeLayout作为一个item布局设置给一个listView的时候,还需要做许多的判断。

由于listView的重用机制,我们这里并未针对listview做任何处理,所以一旦有一个item的SwipeLayout的状态是打开状态,不可避免的其它也必然有几个是打开状态,所以我们这里需要根据检测listView的滑动,当listView滑动时,关闭SwipeLayout。既然需要在外部控制SwipeLayout的开关,我们先定义一个SwipeLayoutManager用于管理SwipeLayout的控制。

public class SwipeLayoutManager {
 //记录打开的SwipeLayout集合
 private HashSet<SwipeLayout> mUnClosedSwipeLayouts = new HashSet<SwipeLayout>();

 private SwipeLayoutManager() {
 }

 private static SwipeLayoutManager mInstance = new SwipeLayoutManager();

 public static SwipeLayoutManager getInstance() {
  return mInstance;
 }

 /**
  * 将一个没有关闭的SwipeLayout加入集合
  * @param layout
  */
 public void add(SwipeLayout layout) {
  mUnClosedSwipeLayouts.add(layout);
 }

 /**
  * 将一个没有关闭的SwipeLayout移出集合
  * @param layout
  */
 public void remove(SwipeLayout layout){
  mUnClosedSwipeLayouts.remove(layout);
 }

 /**
  * 关闭已经打开的SwipeLayout
  */
 public void closeUnCloseSwipeLayout() {
  if(mUnClosedSwipeLayouts.size() == 0){
   return;
  }

  for(SwipeLayout l : mUnClosedSwipeLayouts){
   l.close(true);
  }
  mUnClosedSwipeLayouts.clear();
 }

 /**
  * 关闭已经打开的SwipeLayout
  */
 public void closeUnCloseSwipeLayout(boolean isSmooth) {
  if(mUnClosedSwipeLayouts.size() == 0){
   return;
  }

  for(SwipeLayout l : mUnClosedSwipeLayouts){
   l.close(isSmooth);
  }
  mUnClosedSwipeLayouts.clear();
 }
}

这样就可以监听listView的滑动,然后在listView滑动的时候,关闭所有的抽屉View。

 listView.setOnScrollListener(new OnScrollListener() {
   @Override
   public void onScrollStateChanged(AbsListView view, int scrollState) {
    swipeLayoutManager.closeUnCloseSwipeLayout();
   }

   @Override
   public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
   }
  });

考虑到大多数时候,在我们打开抽屉View和关闭抽屉View的时候,外部需要知道SwipeLayout的状态值,所以我们需要在SwipeLayout中增加几个接口,告诉外部当前SwipeLayout的状态值:

SwipeLayout.java

----------------
 private void updateSwipeStates() {
  SwipeState lastSwipeState = currentState;
  SwipeState swipeState = getCurrentState();

  if (listener == null) {
   try {
    throw new Exception("please setOnSwipeStateChangeListener first!");
   } catch (Exception e) {
    e.printStackTrace();
   }
   return;
  }

  if (swipeState != currentState) {
   currentState = swipeState;
   if (currentState == SwipeState.Open) {
    listener.onOpen(this);
    // 当前的Swipelayout已经打开,需要让Manager记录
    swipeLayoutManager.add(this);
   } else if (currentState == SwipeState.Close) {
    listener.onClose(this);
    // 说明当前的SwipeLayout已经关闭,需要让Manager移除
    swipeLayoutManager.remove(this);
   } else if (currentState == SwipeState.Swiping) {
    if (lastSwipeState == SwipeState.Open) {
     listener.onStartClose(this);
    } else if (lastSwipeState == SwipeState.Close) {
     listener.onStartOpen(this);
     //hideView准备显示之前,先将之前打开的的SwipeLayout全部关闭
     swipeLayoutManager.closeUnCloseSwipeLayout();
     swipeLayoutManager.add(this);
    }
   }
  } else {
   currentState = swipeState;
  }
 }

 /**
  * 获取当前控件状态
  *
  * @return
  */
 public SwipeState getCurrentState() {
  int left = contentView.getLeft();
  //  Log.e("tag", "contentView.getLeft() = " + left);
  //  Log.e("tag", "hideViewWidth = " + hideViewWidth);
  if (left == 0) {
   return SwipeState.Close;
  }

  if (left == -hideViewWidth) {
   return SwipeState.Open;
  }
  return SwipeState.Swiping;
 }
 private OnSwipeStateChangeListener listener;

 public void setOnSwipeStateChangeListener(
   OnSwipeStateChangeListener listener) {
  this.listener = listener;
 }

 public View getContentView() {
  return contentView;
 }

 public interface OnSwipeStateChangeListener {
  void onOpen(SwipeLayout swipeLayout);

  void onClose(SwipeLayout swipeLayout);

  void onStartOpen(SwipeLayout swipeLayout);

  void onStartClose(SwipeLayout swipeLayout);
 }

然后接下来是写一个为listView设置的SwipeAdapter

SwipeAdapter.java

------------
public class SwipeAdapter extends BaseAdapter implements OnSwipeStateChangeListener {
 private Context mContext;
 private List<String> list;
 private MyClickListener myClickListener;
 private SwipeLayoutManager swipeLayoutManager;

 public SwipeAdapter(Context mContext) {
  super();
  this.mContext = mContext;
  init();
 }

 private void init() {
  myClickListener = new MyClickListener();
  swipeLayoutManager = SwipeLayoutManager.getInstance();
 }

 public void setList(List<String> list){
  this.list = list;
  notifyDataSetChanged();
 }

 @Override
 public int getCount() {
  return list.size();
 }

 @Override
 public Object getItem(int position) {
  return list.get(position);
 }

 @Override
 public long getItemId(int position) {
  return position;
 }

 @Override
 public View getView(final int position, View convertView, ViewGroup parent) {
  if (convertView == null) {
   convertView = UIUtils.inflate(R.layout.list_item_swipe);
  }
  ViewHolder holder = ViewHolder.getHolder(convertView);

  holder.tv_content.setText(list.get(position));
  holder.tv_overhead.setOnClickListener(myClickListener);
  holder.tv_overhead.setTag(position);
  holder.tv_delete.setOnClickListener(myClickListener);
  holder.tv_delete.setTag(position);
  holder.sv_layout.setOnSwipeStateChangeListener(this);
  holder.sv_layout.setTag(position);

  holder.sv_layout.getContentView().setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    ToastUtils.showToast("item click : " + position);
    swipeLayoutManager.closeUnCloseSwipeLayout();
   }
  });

  return convertView;
 }

 static class ViewHolder {
  TextView tv_content, tv_overhead, tv_delete;
  SwipeLayout sv_layout;

  public ViewHolder(View convertView) {
   tv_content = (TextView) convertView.findViewById(R.id.tv_content);
   tv_overhead = (TextView) convertView.findViewById(R.id.tv_overhead);
   tv_delete = (TextView) convertView.findViewById(R.id.tv_delete);
   sv_layout = (SwipeLayout) convertView.findViewById(R.id.sv_layout);
  }

  public static ViewHolder getHolder(View convertView) {
   ViewHolder holder = (ViewHolder) convertView.getTag();
   if (holder == null) {
    holder = new ViewHolder(convertView);
    convertView.setTag(holder);
   }
   return holder;
  }
 }

 class MyClickListener implements View.OnClickListener {
  @Override
  public void onClick(View v) {
   Integer position = (Integer) v.getTag();
   switch (v.getId()) {
    case R.id.tv_overhead:
     //ToastUtils.showToast("position : " + position + " overhead is clicked.");
     }
     break;
    case R.id.tv_delete:
     //ToastUtils.showToast("position : " + position + " delete is clicked.");
     }
     break;
    default:
     break;
   }
  }
 }

 @Override
 public void onOpen(SwipeLayout swipeLayout) {
  //ToastUtils.showToast(swipeLayout.getTag() + "onOpen.");
 }

 @Override
 public void onClose(SwipeLayout swipeLayout) {
  //ToastUtils.showToast(swipeLayout.getTag() + "onClose.");
 }

 @Override
 public void onStartOpen(SwipeLayout swipeLayout) {
  //   ToastUtils.showToast("onStartOpen.");
 }

 @Override
 public void onStartClose(SwipeLayout swipeLayout) {
  //   ToastUtils.showToast("onStartClose.");
 }
}

此时已经基本实现了我们需要的大部分功能了,但是当我们滑动的时候,又发现新的问题,我们的SwipeLayout和listview滑动判断有问题。由于前面我们仅仅是将touch拦截事件简简单单的丢给了viewDragHelper.shouldInterceptTouchEvent(ev)来处理,导致SwipeLayout和listview拦截touch事件时的处理存在一定的问题,这里我们要提到一个知识点:Android view事件的传递。
(1)首先由Activity分发,分发给根View,也就是DecorView(DecorView为整个Window界面的最顶层View)
(2)然后由根View分发到子的View

view事件拦截如下图所示:

view事件的消费如下图所示:

注:以上2张图借鉴网上总结的比较经典的图

所以这里我们就要谈到一开始出现的ContentLayout,主要重写了onInterceptTouchEvent和onTouchEvent。

public class ContentLayout extends LinearLayout {
 SwipeLayoutInterface mISwipeLayout;

 public ContentLayout(Context context) {
  super(context);
 }

 public ContentLayout(Context context, AttributeSet attrs) {
  super(context, attrs);
 }

 public ContentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
 }

 public void setSwipeLayout(SwipeLayoutInterface iSwipeLayout) {
  this.mISwipeLayout = iSwipeLayout;
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
//  Log.e("ContentLayout", "-----onInterceptTouchEvent-----");
  if (mISwipeLayout.getCurrentState() == SwipeState.Close) {
   return super.onInterceptTouchEvent(ev);
  } else {
   return true;
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent ev) {
//  Log.e("ContentLayout", "-----onTouchEvent-----");
  if (mISwipeLayout.getCurrentState() == SwipeState.Close) {
   return super.onTouchEvent(ev);
  } else {
   if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
    mISwipeLayout.close();
   }
   return true;
  }
 }
}

另外由于在ContentLayout中需要拿到父View SwipeLayout的开关状态以及控制SwipeLayout的关闭,因此在再写一个接口,用于ContentLayout获取SwipeLayout的开关状态以及更新SwipeLayout。

public interface SwipeLayoutInterface {

 SwipeState getCurrentState();

 void open();

 void close();
}

然后接着的是完善SwipeLayout的onInterceptTouchEvent,我们在这里增加一个GestureDetectorCompat处理手势识别:

 private void init(Context context) {
  viewDragHelper = ViewDragHelper.create(this, callback);
  mGestureDetector = new GestureDetectorCompat(context, mOnGestureListener);
  swipeLayoutManager = SwipeLayoutManager.getInstance();
 }

 private SimpleOnGestureListener mOnGestureListener = new SimpleOnGestureListener() {
  @Override
  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
   // 当横向移动距离大于等于纵向时,返回true
   return Math.abs(distanceX) >= Math.abs(distanceY);
  }
 };
  @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  boolean result = viewDragHelper.shouldInterceptTouchEvent(ev) & mGestureDetector.onTouchEvent(ev);
  //  Log.e("SwipeLayout", "-----onInterceptTouchEvent-----");
  return result;
 }

如此下来,整个View不管是上下拖动,还是SwipeLayout的开关滑动,都已经实现完成了。最后增加对应overhead,delete以及item的点击事件,此处完善SwipeAdapter的代码之后如下。

 class MyClickListener implements View.OnClickListener {
  @Override
  public void onClick(View v) {
   Integer position = (Integer) v.getTag();
   switch (v.getId()) {
    case R.id.tv_overhead:
     //ToastUtils.showToast("position : " + position + " overhead is clicked.");
     swipeLayoutManager.closeUnCloseSwipeLayout(false);
     if(onSwipeControlListener != null){
      onSwipeControlListener.onOverhead(position, list.get(position));
     }
     break;
    case R.id.tv_delete:
     //ToastUtils.showToast("position : " + position + " delete is clicked.");
     swipeLayoutManager.closeUnCloseSwipeLayout(false);
     if(onSwipeControlListener != null){
      onSwipeControlListener.onDelete(position, list.get(position));
     }
     break;
    default:
     break;
   }
  }
 }

 private OnSwipeControlListener onSwipeControlListener;

 public void setOnSwipeControlListener(OnSwipeControlListener onSwipeControlListener){
  this.onSwipeControlListener = onSwipeControlListener;
 }

 /**
  * overhead 和 delete点击事件接口
  */
 public interface OnSwipeControlListener{
  void onOverhead(int position, String itemTitle);

  void onDelete(int position, String itemTitle);
 }

最后贴上MainActivity代码,此处通过OnSwipeControlListener接口回调实现item的删除和置顶:

public class MainActivity extends Activity implements OnSwipeControlListener {
 private ListView listView;
 private List<String> list = new ArrayList<String>();
 private SwipeLayoutManager swipeLayoutManager;
 private SwipeAdapter swipeAdapter;

 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  initData();
  initView();
 }

 private void initData() {
  for (int i = 0; i < 50; i++) {
   list.add("content - " + i);
  }
 }

 private void initView() {
  swipeLayoutManager = SwipeLayoutManager.getInstance();
  swipeAdapter = new SwipeAdapter(this);
  swipeAdapter.setList(list);

  listView = (ListView) findViewById(R.id.list_view);

  listView.setAdapter(swipeAdapter);
  listView.setOnScrollListener(new OnScrollListener() {
   @Override
   public void onScrollStateChanged(AbsListView view, int scrollState) {
    swipeLayoutManager.closeUnCloseSwipeLayout();
   }

   @Override
   public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
   }
  });

  swipeAdapter.setOnSwipeControlListener(this);
 }

 @Override
 public void onOverhead(int position, String itemTitle) {
  setItemOverhead(position, itemTitle);
 }

 @Override
 public void onDelete(int position, String itemTitle) {
  removeItem(position, itemTitle);
 }

 /**
  * 设置item置顶
  *
  * @param position
  * @param itemTitle
  */
 private void setItemOverhead(int position, String itemTitle) {
  // ToastUtils.showToast("position : " + position + " overhead.");
  ToastUtils.showToast("overhead ---" + itemTitle + "--- success.");
  String newTitle = itemTitle;
  list.remove(position);//删除要置顶的item
  list.add(0, newTitle);//根据adapter传来的Title数据在list 0位置插入title字符串,达到置顶效果
  swipeAdapter.setList(list);//重新给Adapter设置list数据并更新
  UIUtils.runOnUIThread(new Runnable() {
   @Override
   public void run() {
    listView.setSelection(0);//listview选中第0项item
   }
  });
 }

 /**
  * 删除item
  *
  * @param position
  * @param itemTitle
  */

 private void removeItem(int position, String itemTitle) {
  //  ToastUtils.showToast("position : " + position + " delete.");
  ToastUtils.showToast("delete ---" + itemTitle + "--- success.");
  list.remove(position);
  swipeAdapter.setList(list);//重新给Adapter设置list数据并更新
 }
}

至此整个demo基本完成,本次完成的功能基本能够直接放到项目中使用。其实最麻烦的地方就在于view的touch事件拦截和处理,不过将本demo的log打开看一下对比之后,也就能够理解整个传递过程了。

完整demo地址:https://github.com/Horrarndoo/SwipeLayout

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android DrawerLayout实现抽屉效果实例代码

    官网:https://developer.android.com/training/implementing-navigation/nav-drawer.html 贴上主要的逻辑和布局文件: activity_main.xml <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schema

  • Android App中DrawerLayout抽屉效果的菜单编写实例

    抽屉效果的导航菜单 看了很多应用,觉得这种侧滑的抽屉效果的菜单很好. 不用切换到另一个页面,也不用去按菜单的硬件按钮,直接在界面上一个按钮点击,菜单就滑出来,而且感觉能放很多东西. 库的引用: 首先, DrawerLayout这个类是在Support Library里的,需要加上android-support-v4.jar这个包. 然后程序中用时在前面导入import android.support.v4.widget.DrawerLayout; 如果找不到这个类,首先用SDK Manager更

  • Android 自定义View实现抽屉效果

    Android 自定义View实现抽屉效果 说明 这个自定义View,没有处理好多点触摸问题 View跟着手指移动,没有采用传统的scrollBy方法,而是通过不停地重新布局子View的方式,来使得子View产生滚动效果menuView.layout(menuLeft, 0, menuLeft + menuWidth, menuHeight); 相应的,由于没有使用scrollBy方法,就没有产生getScrollX值,所以不能通过Scroller的startScroll方法来完成手指离开后的平

  • Android实现自定义滑动式抽屉效果菜单

    在Andoird使用Android自带的那些组件,像SlidingDrawer和DrawerLayout都是抽屉效果的菜单,但是在项目很多要实现的功能都收到Android这些自带组件的限制,导致很难完成项目的需求,自定义的组件,各方面都在自己的控制之下,从而根据需求做出调整.想要实现好的效果,基本上都的基于Android的OnTouch事件自己实现响应的功能. 首先,给大家先看一下整体的效果: 滑动的加速度效果都是有的,具体的体验,只能安装后才能查看. 接下来,看代码: 代码从MainActiv

  • Android Tween动画之RotateAnimation实现图片不停旋转效果实例介绍

    主要介绍Android中如何使用rotate实现图片不停旋转的效果.Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换(平移.缩放.旋转)产生动画效果:第二类是 Frame 动画,即顺序播放事先做好的图像,跟电影类似.本文分析 Tween动画的rotate实现旋转效果. 在新浪微博客户端中各个操作进行中时activity的右上角都会有个不停旋转的图标,类似刷新的效果,给用户以操作中的提示.这种非模态的提示方式推荐使用,那么下面就分享下如何实现这种效果

  • Android编程实现抽屉效果的方法详解

    本文实例讲述了Android编程实现抽屉效果的方法.分享给大家供大家参考,具体如下: android的UI开发确实是一件很有趣的事情,也是一件很有挑战性的事情. 本文章是将自己在开发中的项目中使用到的比较好的抽屉效果的原理以及代码整理后写上来的,以备忘记后可以查阅 抽屉效果的原理很简单,就是给其一个事件监听(动作),然后对此动作所作出的反应(开 or 关): 在编写代码的时候要注意的几点如下: 1. 打开抽屉的图标(即触发抽屉的把手),打开后的界面都是布局在<SlidingDrawer/>与&

  • Android开发之DrawerLayout实现抽屉效果

    谷歌官方推出了一种侧滑菜单的实现方式(抽屉效果),即 DrawerLayout,这个类是在Support Library里的,需要加上android-support-v4.jar这个包. 使用注意点 1.DrawerLayout的第一个子元素必须是默认内容,即抽屉没有打开时显示的布局(如FrameLayout),后面紧跟的子元素是抽屉内容,即抽屉布局(如ListView). 2.抽屉菜单的摆放和布局通过android:layout_gravity属性来控制,可选值为left.right或star

  • Android的Activity跳转动画各种效果整理

    大家使用Android的原生UI都知道,Android的Activity跳转就是很生硬的切换界面.其实Android的Activity跳转可以设置各种动画.下面给大家看看效果:  实现非常简单,用overridePendingtransition(int inId, int outId)即可实现.inId是下一界面进入效果的xml文件的id,outId是当前界面退出效果的xml文件id. 效果是用xml文件写的,首先要在res文件夹下建立anim文件夹,然后把动画效果xml文件放到里面去. 下面

  • Android实现图片轮播效果的两种方法

    大家在使用APP的过程中,经常会看到上部banner图片轮播的效果,那么今天我们就一起来学习一下,android中图片轮询的几种实现方法: 第一种:使用动画的方法实现:(代码繁琐) 这种发放需要:两个动画效果,一个布局,一个主类来实现,不多说了,来看代码吧: public class IamgeTrActivity extends Activity { /** Called when the activity is first created. */ public ImageView image

  • Android编程实现抽屉效果的方法示例

    本文实例讲述了Android编程实现抽屉效果的方法.分享给大家供大家参考,具体如下: 今天在手机上实现了抽屉效果,其实很简单,但是效果却很酷. 首先在layout 下设置xml布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:l

  • Android 抽屉效果的导航菜单实现代码实例

    看了很多应用,觉得这种侧滑的抽屉效果的菜单很好. 不用切换到另一个页面,也不用去按菜单的硬件按钮,直接在界面上一个按钮点击,菜单就滑出来,而且感觉能放很多东西. 关于实现,搜索了一下,有如下两种: 1.用SlidingDrawer:http://developer.android.com/reference/android/widget/SlidingDrawer.html 但是不知道为什么这个类官方不建议再继续用了:Deprecated since API level 17 2.用Drawer

  • Android SlidingDrawer 抽屉效果的实现

    SlidingDrawer隐藏屏外的内容,并允许用户通过handle以显示隐藏内容.它可以垂直或水平滑动,它有俩个View组成,其一是可以拖动的handle,其二是隐藏内容的View.它里面的控件必须设置布局,在布局文件中必须指定handle和content. 1.布局layou文件 复制代码 代码如下: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_w

随机推荐