QQ 特效学习 二 侧滑删除
上篇文章: http://www.cnblogs.com/xurui1995/p/5798631.html
今天来写不仅是qq而且在别的软件上也特别流行的侧滑删除
其实套路和前篇的一样,一个自定义View继承FrameLayout,然后利用ViewDragHelper。
效果:

首先侧滑删除是放在listview中的,将我们的自定义View当做Item。
接着昨天的代码往后写
首先是layout_content
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.xw.qqslidemenu.SwipeLayout android:id="@+id/swipeLayout" android:layout_width="match_parent" android:layout_height="wrap_content" > <include layout="@layout/layout_content"/> <include layout="@layout/layout_delete"/> </com.example.xw.qqslidemenu.SwipeLayout> </LinearLayout>
然后是layout_delete
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.xw.qqslidemenu.SwipeLayout android:id="@+id/swipeLayout" android:layout_width="match_parent" android:layout_height="wrap_content" > <include layout="@layout/layout_content"/> <include layout="@layout/layout_delete"/> </com.example.xw.qqslidemenu.SwipeLayout> </LinearLayout>
layout中添加item.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.xw.qqslidemenu.SwipeLayout android:id="@+id/swipeLayout" android:layout_width="match_parent" android:layout_height="wrap_content" > <include layout="@layout/layout_content"/> <include layout="@layout/layout_delete"/> </com.example.xw.qqslidemenu.SwipeLayout> </LinearLayout>
这里 用到了自定义的SwipeLayout,
自定义SwipeLayout还是昨天的套路
package com.example.xw.qqslidemenu; import android.content.Context; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; /** * Created by xw on 2016/8/24. */ public class SwipeLayout extends FrameLayout { private View contentView; private View deleteView; private ViewDragHelper viewDragHelper; private int deleteHeight;//delete区域的高度,也当做content高度 private int deleteWidth;//delete宽度 private int contentWidth;//content宽度 public SwipeLayout(Context context) { super(context); init(); } public SwipeLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @Override protected void onFinishInflate() { super.onFinishInflate(); contentView=getChildAt(0); deleteView=getChildAt(1); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); deleteWidth=deleteView.getMeasuredWidth(); deleteHeight=deleteView.getMeasuredHeight(); contentWidth=contentView.getMeasuredWidth(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { contentView.layout(0,0,contentWidth,deleteHeight); deleteView.layout(contentView.getRight(),0,contentView.getRight()+deleteWidth,deleteHeight); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return viewDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { viewDragHelper.processTouchEvent(event); return true; } private void init() { viewDragHelper=ViewDragHelper.create(this,callback); } private ViewDragHelper.Callback callback=new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { return child==contentView||child==deleteView; } @Override public int getViewHorizontalDragRange(View child) { return deleteWidth; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { if(child==deleteView){ if(left<contentWidth-deleteWidth) left=contentWidth-deleteWidth; if(left>contentWidth) left=contentWidth; } if(child==contentView){ if(left<-deleteWidth) left=-deleteWidth; if(left>0) left=0; } 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.layout(deleteView.getLeft()+dx,deleteView.getTop()+dy, deleteView.getRight()+dx,deleteView.getBottom()+dy); } if(changedView==deleteView){ contentView.layout(contentView.getLeft()+dx,contentView.getTop()+dy, contentView.getRight()+dx, contentView.getBottom()+dy); } } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); if(contentView.getLeft()<-deleteWidth/2){ //应该打开 open(); }else { //应该关闭 close(); } } }; public void computeScroll() { if(viewDragHelper.continueSettling(true)){ ViewCompat.postInvalidateOnAnimation(this); } } public void close() { viewDragHelper.smoothSlideViewTo(contentView,0,contentView.getTop()); ViewCompat.postInvalidateOnAnimation(SwipeLayout.this); } public void open() { viewDragHelper.smoothSlideViewTo(contentView,-deleteWidth,contentView.getTop()); ViewCompat.postInvalidateOnAnimation(SwipeLayout.this); } }
修改MainActivity主界面listView中的Adapter
package com.example.xw.qqslidemenu; import android.graphics.Color; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.view.animation.CycleInterpolator; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import com.nineoldandroids.view.ViewHelper; import com.nineoldandroids.view.ViewPropertyAnimator; import java.util.Random; public class MainActivity extends AppCompatActivity { private ListView mainlv; private ListView menulv; private SlideMenu slideMenu; private ImageView iv_head; private MyLinearLayout my_layout; public static final String[] sCheeseStrings = { "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi", "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese", "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell", "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc", "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese", "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza", "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley", "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano" }; public static final String[] NAMES = new String[] { "宋江", "卢俊义", "吴用", "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智深", "武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", "穆弘", "雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", "解珍", " 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪", "魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方", "郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", "项充", "李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿", "陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永", "施恩", }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); iv_head= (ImageView) findViewById(R.id.iv_head); mainlv= (ListView) findViewById(R.id.main_listview); menulv= (ListView) findViewById(R.id.menu_listview); mainlv.setAdapter(new MyAdapter()); menulv.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,sCheeseStrings){ @Override //改变textView颜色 public View getView(int position, View convertView, ViewGroup parent) { TextView textView = (TextView) super.getView(position, convertView, parent); textView.setTextColor(Color.WHITE); return textView; } }); slideMenu= (SlideMenu) findViewById(R.id.slideMenu); //给my_layout设置slideMenu my_layout = (MyLinearLayout) findViewById(R.id.my_layout); my_layout.setSlideMenu(slideMenu); slideMenu.setOnDragStateChangeListener(new SlideMenu.OnDragStateChangeListener() { @Override public void onOpen() { menulv.smoothScrollToPosition(new Random().nextInt(menulv.getCount())); } @Override public void onClose() { ViewPropertyAnimator.animate(iv_head).translationXBy(15) .setInterpolator(new CycleInterpolator(4)) .setDuration(500) .start(); } @Override public void onDraging(Float fraction) { ViewHelper.setAlpha(iv_head,1-fraction); } }); } class MyAdapter extends BaseAdapter{ @Override public int getCount() { return NAMES.length; } @Override public Object getItem(int i) { return null; } @Override public long getItemId(int i) { return 0; } @Override public View getView(int i, View view, ViewGroup viewGroup) { View v; if(view==null){ v=View.inflate(MainActivity.this,R.layout.item,null); } else{ v=view; } TextView tvname= (TextView) v.findViewById(R.id.tv_name); tvname.setText(NAMES[i]); return v; } } }
运行效果:

这里我们发现有两个bug
1:deleteview右滑回来时会同时主界面右滑跳出菜单界面,造成冲突
2:listview下拉时,上方打开的deleteview不关闭,导致listview的复用出错。
接下来,开始改这两个bug。
整体思路是:
1。对每个SwipeLayout设置两个状态,打开和关闭
2。当存在某个deleteview打开时,点击lisetview其他条目或者滑动时,该条目会先关闭
3。当存在某个deleteview打开时,侧滑出菜单栏应该无效,也就是说必须要全部关闭时才能划出侧边栏。
添加SwipeLayoutManager类
package com.example.xw.qqslidemenu; /** * Created by xw on 2016/8/25. */ public class SwipeLayoutManager { private SwipeLayoutManager(){} private static SwipeLayoutManager mInstance = new SwipeLayoutManager(); public static SwipeLayoutManager getInstance(){ return mInstance; } private SwipeLayout currentLayout;//用来记录当前打开的SwipeLayout public void setSwipeLayout(SwipeLayout layout){ this.currentLayout = layout; } /** * 清空当前所记录的已经打开的layout */ public void clearCurrentLayout(){ currentLayout = null; } public boolean isallClose(){ return currentLayout==null; } /** * 关闭当前已经打开的SwipeLayout */ public void closeCurrentLayout(){ if(currentLayout!=null){ currentLayout.close(); } } /** * 判断当前是否应该能够滑动,如果没有打开的,则可以滑动。 * 如果有打开的,则判断打开的layout和当前按下的layout是否是同一个 * * @return */ public boolean isShouldSwipe(SwipeLayout swipeLayout){ if(currentLayout==null){ //说明当前木有打开的layout return true; }else { //说明有打开的layout return currentLayout==swipeLayout; } } }
修改SwipeLayout类。添加两种状态以及修改拦截和Touch事件。
package com.example.xw.qqslidemenu; import android.content.Context; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; /** * Created by xw on 2016/8/24. */ public class SwipeLayout extends FrameLayout { private View contentView; private View deleteView; private ViewDragHelper viewDragHelper; private int deleteHeight;//delete区域的高度,也当做content高度 private int deleteWidth;//delete宽度 private int contentWidth;//content宽度 public SwipeLayout(Context context) { super(context); init(); } public SwipeLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } enum SwipeState{ Open,Close; } private SwipeState currentState=SwipeState.Close;//默认是关闭 @Override protected void onFinishInflate() { super.onFinishInflate(); contentView=getChildAt(0); deleteView=getChildAt(1); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); deleteWidth=deleteView.getMeasuredWidth(); deleteHeight=deleteView.getMeasuredHeight(); contentWidth=contentView.getMeasuredWidth(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { contentView.layout(0,0,contentWidth,deleteHeight); deleteView.layout(contentView.getRight(),0,contentView.getRight()+deleteWidth,deleteHeight); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean result = viewDragHelper.shouldInterceptTouchEvent(ev); //如果当前有打开的,则需要直接拦截,交给onTouch处理 if(!SwipeLayoutManager.getInstance().isShouldSwipe(this)){ //先关闭已经打开的layout SwipeLayoutManager.getInstance().closeCurrentLayout(); result = true; } return result; } private float downX,downY; @Override public boolean onTouchEvent(MotionEvent event) { //如果全部是关闭的,不拦截listview if (SwipeLayoutManager.getInstance().isallClose()){ viewDragHelper.processTouchEvent(event); return true; } //如果当前有打开的,则下面的逻辑不能执行 if(!SwipeLayoutManager.getInstance().isShouldSwipe(this)){ requestDisallowInterceptTouchEvent(true); return true; } switch (event.getAction()){ case MotionEvent.ACTION_DOWN: downX=event.getX(); downY=event.getY(); break; case MotionEvent.ACTION_MOVE: 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不要拦截 requestDisallowInterceptTouchEvent(true); } downX = moveX; downY = moveY; break; } viewDragHelper.processTouchEvent(event); return true; } private void init() { viewDragHelper=ViewDragHelper.create(this,callback); } private ViewDragHelper.Callback callback=new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { return child==contentView||child==deleteView; } @Override public int getViewHorizontalDragRange(View child) { return deleteWidth; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { if(child==deleteView){ if(left<contentWidth-deleteWidth) left=contentWidth-deleteWidth; if(left>contentWidth) left=contentWidth; } if(child==contentView){ if(left<-deleteWidth) left=-deleteWidth; if(left>0) left=0; } 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.layout(deleteView.getLeft()+dx,deleteView.getTop()+dy, deleteView.getRight()+dx,deleteView.getBottom()+dy); } if(changedView==deleteView){ contentView.layout(contentView.getLeft()+dx,contentView.getTop()+dy, contentView.getRight()+dx, contentView.getBottom()+dy); } //判断开和关闭的逻辑 if(contentView.getLeft()==0 && currentState!=SwipeState.Close){ //说明应该将state更改为关闭 currentState = SwipeState.Close; //说明当前的SwipeLayout已经关闭,需要让Manager清空一下 SwipeLayoutManager.getInstance().clearCurrentLayout(); } else if (contentView.getLeft()==-deleteWidth && currentState!=SwipeState.Open) { //说明应该将state更改为开 currentState = SwipeState.Open; SwipeLayoutManager.getInstance().setSwipeLayout(SwipeLayout.this); } } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); if(contentView.getLeft()<-deleteWidth/2){ //应该打开 open(); }else { //应该关闭 close(); } } }; public void computeScroll() { if(viewDragHelper.continueSettling(true)){ ViewCompat.postInvalidateOnAnimation(this); } } public void close() { viewDragHelper.smoothSlideViewTo(contentView,0,contentView.getTop()); ViewCompat.postInvalidateOnAnimation(SwipeLayout.this); } public void open() { viewDragHelper.smoothSlideViewTo(contentView,-deleteWidth,contentView.getTop()); ViewCompat.postInvalidateOnAnimation(SwipeLayout.this); } }
完成。

浙公网安备 33010602011771号