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);
    }
}

 

完成。

 

posted @ 2016-08-25 15:52  zerocoin  阅读(398)  评论(0)    收藏  举报