自定义控件那些事儿 ----- 左滑删除控件

来源:互联网 发布:java 动态合成图片 编辑:程序博客网 时间:2024/05/17 08:11

左滑删除使用比较多一点,在网上也有不少资料实现,不过坑总是不少。

整合资料做出来一个效果,大家看看依据效果选择是否需要继续研究。



一、自定义控件的实现

1,自定义控件的实现

实现思路主要是:将整体内容分为主体内容和左滑显示内容,布局文件中必须有两个子类。在依据ViewDragHelper的帮助计算主体内容和左滑内容的显示以及显示多少。


/** * 左滑删除 * 2017/12/11. */public class SwipeLayout extends FrameLayout {    private ViewDragHelper dragHelper;    private OnSwipeChangeListener swipeChangeListener;    private View backView;//侧滑菜单    private View frontView;//内容区域    private int height;//自定义控件布局高    private int width;//自定义控件布局宽    private int range;//侧滑菜单可滑动范围    public boolean isOpen() {        return isOpen;    }    private boolean isOpen = false;//是否是打开状态    public void setOpen(boolean open) {        isOpen = open;    }    //重写三个构造方法    public SwipeLayout(Context context) {        this(context, null);    }    public SwipeLayout(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        dragHelper = ViewDragHelper.create(this, callback);    }    //获取两个View    protected void onFinishInflate() {        super.onFinishInflate();        int childCount = getChildCount();        if (childCount < 2) {            throw new IllegalStateException("you need 2 children view");        }        if (!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)) {            throw new IllegalArgumentException("your children must be instance of ViewGroup");        }        backView = getChildAt(0);//侧滑菜单        frontView = getChildAt(1);//内容区域    }    //初始化布局的高height宽width以及可滑动的范围range    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        height = frontView.getMeasuredHeight();        width = frontView.getMeasuredWidth();        range = backView.getMeasuredWidth();    }    //布局子View    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {//        super.onLayout(changed, left, top, right, bottom);//        layoutContent(false);        layoutContent(isOpen);    }    /**     * @param isOpen 侧滑菜单是否打开     */    private void layoutContent(boolean isOpen) {        Rect frontRect = computeFrontViewRect(isOpen);        frontView.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom);        Rect backRect = computeBackViewRect(frontRect);        backView.layout(backRect.left, backRect.top, backRect.right, backRect.bottom);        //调整顺序//        bringChildToFront(backView);    }    /**     * 通过内容区域所占矩形坐标计算侧滑菜单的矩形位置区域     *     * @param frontRect 内容区域所占矩形     * @return     */    private Rect computeBackViewRect(Rect frontRect) {        int left = frontRect.right;        return new Rect(left, 0, left + range, height);    }    /**     * 通过菜单打开与否isOpen计算内容区域的矩形区     *     * @param isOpen     * @return     */    private Rect computeFrontViewRect(boolean isOpen) {        int left = 0;        if (isOpen) {            left = -range;        }        return new Rect(left, 0, left + width, height);    }    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {        //所有子View都可拖拽        public boolean tryCaptureView(View child, int pointerId) {            return true;        }        //水平拖拽后处理        public int clampViewPositionHorizontal(View child, int left, int dx) {            if (child == frontView) {                if (left > 0) {                    return 0;                } else if (left < -range) {                    return -range;                }            } else if (child == backView) {                if (left > width) {                    return width;                } else if (left < width - range) {                    return width - range;                }            }            return left;        }        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {            if (changedView == frontView) {                backView.offsetLeftAndRight(dx);            } else if (changedView == backView) {                frontView.offsetLeftAndRight(dx);            }            //事件派发            dispatchSwipeEvent();            //兼容低版本            invalidate();        }        //松手后根据侧滑位移确定菜单打开与否        public void onViewReleased(View releasedChild, float xvel, float yvel) {            if (xvel == 0 && frontView.getLeft() < -range * 0.3f) {//-range * 0.5f  修改灵敏度                open();            } else if (xvel < 0) {                open();            } else {                close();            }        }        //子View如果是clickable,必须重写的方法        public int getViewHorizontalDragRange(View child) {            return 1;        }        public int getViewVerticalDragRange(View child) {            return 1;        }    };    public boolean onInterceptTouchEvent(MotionEvent ev) {        return dragHelper.shouldInterceptTouchEvent(ev);    }    public boolean onTouchEvent(MotionEvent event) {        dragHelper.processTouchEvent(event);        return true;    }    // 持续平滑动画 高频调用    public void computeScroll() {        // 如果返回true,动画还需要继续        if (dragHelper.continueSettling(true)) {            ViewCompat.postInvalidateOnAnimation(this);        }    }    public void open() {        open(true);    }    public void open(boolean isSmooth) {        int finalLeft = -range;        if (isSmooth) {            if (dragHelper.smoothSlideViewTo(frontView, finalLeft, 0)) {                ViewCompat.postInvalidateOnAnimation(this);            }            //布局方式调整     /*       else {                layoutContent(true);//添加部分,执行OnLayout中方法,效果一样            }*/        } else {            layoutContent(true);        }    }    public void close() {        close(true);    }    public void close(boolean isSmooth) {        int finalLeft = 0;        if (isSmooth) {            if (dragHelper.smoothSlideViewTo(frontView, finalLeft, 0)) {                ViewCompat.postInvalidateOnAnimation(this);            }        } else {            layoutContent(false);        }    }    private Status status = Status.CLOSE;//拖拽状态 默认关闭    public static enum Status {        OPEN, CLOSE, DRAGING    }    //拖拽事件监听器    public static interface OnSwipeChangeListener {        void onDraging(SwipeLayout mSwipeLayout);        void onOpen(SwipeLayout mSwipeLayout);        void onClose(SwipeLayout mSwipeLayout);        void onStartOpen(SwipeLayout mSwipeLayout);        void onStartClose(SwipeLayout mSwipeLayout);    }    //更改状态    private Status updateStatus() {        int left = frontView.getLeft();        if (left == 0) {            return Status.CLOSE;        } else if (left == -range) {            return Status.OPEN;        }        return Status.DRAGING;    }    //根据当前状态判断回调事件    protected void dispatchSwipeEvent() {        Status preStatus = status;        status = updateStatus();        if (swipeChangeListener != null) {            swipeChangeListener.onDraging(this);        }        if (preStatus != status && swipeChangeListener != null) {            if (status == Status.CLOSE) {                swipeChangeListener.onClose(this);            } else if (status == Status.OPEN) {                swipeChangeListener.onOpen(this);            } else if (status == Status.DRAGING) {                if (preStatus == Status.CLOSE) {                    swipeChangeListener.onStartOpen(this);                } else if (preStatus == Status.OPEN) {                    swipeChangeListener.onStartClose(this);                }            }        }    }    public void setSwipeChangeListener(OnSwipeChangeListener swipeChangeListener) {        this.swipeChangeListener = swipeChangeListener;    }}

2,注意点

(1)onLayout()中修改重新计算布局的控制方式 --- 解决整体管理时,显示出来的部分能够显示左滑内容,而新滑动出来的内容不能正常显示。

(2)在滑动最后状态结束的地方调整比率,可以适当提高反应灵敏度;

(3)该控件放置在可以左滑的控件(ViewPager)中,滑动事件会冲突,效果使用不好。



二、自定义控件的使用

1,布局中使用  ListView 的Item布局中


<?xml version="1.0" encoding="utf-8"?><com.future.leftdragdeletedemo.view.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/swipeLayout"    android:layout_width="match_parent"    android:layout_height="60dp">    <LinearLayout        android:layout_width="wrap_content"        android:layout_height="match_parent">        <TextView            android:layout_width="70dp"            android:layout_height="match_parent"            android:background="#ccc"            android:gravity="center"            android:text="置顶"            android:textColor="#fff" />        <TextView            android:id="@+id/tv_del"            android:layout_width="70dp"            android:layout_height="match_parent"            android:background="#f00"            android:clickable="true"            android:gravity="center"            android:text="刪除"            android:textColor="#fff" />    </LinearLayout>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:gravity="center_vertical">        <ImageView            android:id="@+id/imageView"            android:layout_width="50dp"            android:layout_height="50dp"            android:layout_marginLeft="8dp"            android:layout_marginRight="8dp"            android:src="@mipmap/kxg" />        <TextView            android:id="@+id/textView"            android:layout_width="wrap_content"            android:layout_height="match_parent"            android:gravity="center_vertical"            android:textColor="#444"            android:textSize="18sp" />    </LinearLayout></com.future.leftdragdeletedemo.view.SwipeLayout>

2,主布局中使用


<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.future.leftdragdeletedemo.MainActivity">    <RelativeLayout        android:id="@+id/head_rl"        android:layout_width="match_parent"        android:layout_height="50dp">        <ImageView            android:id="@+id/head_back_iv"            android:layout_width="wrap_content"            android:layout_height="match_parent"            android:layout_centerVertical="true"            android:layout_marginLeft="10dp"            android:src="@mipmap/head_backpic" />        <TextView            android:id="@+id/title_tv"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:text="控制我自己"            android:textSize="18sp" />        <TextView            android:id="@+id/whole_control_tv"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentRight="true"            android:layout_centerVertical="true"            android:layout_marginRight="12dp"            android:text="管理"            android:textSize="18sp" />    </RelativeLayout>    <ListView        android:id="@+id/listView"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_below="@+id/head_rl"        android:choiceMode="singleChoice" /></RelativeLayout>

3,适配器实现


public class ListViewAdapter extends BaseAdapter {    private Context context;    private List<ItemBean> dataList;    /**     * 是否是打开状态     */    private boolean isOpen;    public ListViewAdapter(Context context, List<ItemBean> dataList) {        this.context = context;        this.dataList = dataList;    }    /**     * 设置全部打开状态     *     * @param open     */    public void setOpenStatus(boolean open) {        isOpen = open;        if (dataList != null && dataList.size() > 0) {            for (int i = 0; i < dataList.size(); i++) {                dataList.get(i).setOpen(open);            }        }        notifyDataSetChanged();    }    //存放所有已经打开的菜单    private List<SwipeLayout> openList = new ArrayList<SwipeLayout>();    @Override    public int getCount() {        return dataList.size();    }    @Override    public Object getItem(int position) {        return dataList.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(final int position, View convertView, ViewGroup parent) {        final ViewHolder holder;        if (convertView == null) {            holder = new ViewHolder();            convertView = LayoutInflater.from(context).inflate(R.layout.layout_item, parent, false);            holder.textView = (TextView) convertView.findViewById(R.id.textView);            holder.imageView = (ImageView) convertView.findViewById(R.id.imageView);            holder.tv_del = (TextView) convertView.findViewById(R.id.tv_del);            holder.swipeLayout = (SwipeLayout) convertView.findViewById(R.id.swipeLayout);            convertView.setTag(holder);        } else {            holder = (ViewHolder) convertView.getTag();        }        final ItemBean bean = dataList.get(position);        holder.textView.setText(bean.getDescContent());        holder.imageView.setImageResource(bean.getPicIndex());        holder.tv_del.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                holder.swipeLayout.close();                Toast.makeText(context, bean.getDescContent(), Toast.LENGTH_SHORT).show();            }        });        if (bean.isOpen()) {            holder.swipeLayout.setOpen(true);            holder.swipeLayout.open();        } else {            holder.swipeLayout.setOpen(false);            holder.swipeLayout.close();        }        holder.swipeLayout.setSwipeChangeListener(new SwipeLayout.OnSwipeChangeListener() {            @Override            public void onStartOpen(SwipeLayout mSwipeLayout) {                if (!isOpen) {                    for (SwipeLayout layout : openList) {                        layout.close();                    }                    openList.clear();                }            }            @Override            public void onStartClose(SwipeLayout mSwipeLayout) {            }            @Override            public void onOpen(SwipeLayout mSwipeLayout) {                openList.add(mSwipeLayout);                bean.setOpen(true);            }            @Override            public void onDraging(SwipeLayout mSwipeLayout) {            }            @Override            public void onClose(SwipeLayout mSwipeLayout) {                openList.remove(mSwipeLayout);                bean.setOpen(false);            }        });        return convertView;    }    private class ViewHolder {        SwipeLayout swipeLayout;        TextView textView;        TextView tv_del;        ImageView imageView;    }}

4,主类实现


public class MainActivity extends AppCompatActivity implements View.OnClickListener {    /**     * 内容控件     */    private ListView listView;    /**     * 返回     */    private ImageView headBack;    /**     * 控制按钮     */    private TextView controlTV;    private ListViewAdapter adapter;    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        requestWindowFeature(Window.FEATURE_NO_TITLE);        setContentView(R.layout.activity_main);        initView();        initData();        initListener();    }    /**     * 初始化控件     */    private void initView() {        headBack = findViewById(R.id.head_back_iv);        controlTV = findViewById(R.id.whole_control_tv);        listView = (ListView) findViewById(R.id.listView);    }    /**     * 初始化数据     */    private void initData() {        List<ItemBean> list = new ArrayList<>();        ItemBean item = new ItemBean();        for (int i = 0; i < strs.length; i++) {            item.setDescContent(strs[i]);            item.setPicIndex(ids[i]);            list.add(item);            item = new ItemBean();//不添加是同一个??        }        adapter = new ListViewAdapter(MainActivity.this, list);        listView.setAdapter(adapter);    }    /**     * 初始化监听器     */    private void initListener() {        headBack.setOnClickListener(this);        controlTV.setOnClickListener(this);    }    private String strs[] = {"得劲", "加油", "坚持", "慎独", "控制情绪", "好好对待自己", "更美好的未来",            "下一站", "懵懂", "秋高气爽", "嘟噜噜", "就这样吧", "还挺不错的", "普通的小孩"};    private int ids[] = {R.mipmap.kxg, R.mipmap.mmi, R.mipmap.geu, R.mipmap.feb, R.mipmap.fdy, R.mipmap.ewo, R.mipmap.mdt,            R.mipmap.kxg, R.mipmap.mmi, R.mipmap.geu, R.mipmap.feb, R.mipmap.fdy, R.mipmap.ewo, R.mipmap.mdt};    @Override    public void onClick(View view) {        switch (view.getId()) {            //返回            case R.id.head_back_iv:                finish();                break;            //管理            case R.id.whole_control_tv:                String desc = controlTV.getText().toString().trim();                if ("管理".equals(desc)) {                    controlTV.setText("取消");                    adapter.setOpenStatus(true);                } else {                    controlTV.setText("管理");                    adapter.setOpenStatus(false);                }                break;        }    }    /**     * 问题备注:     * 1,list压入数据时,需要新创建bean对象;-----数据处理     * 2,上下滑动,左滑状态不能保持; ------   bean类中添加控制变量,适配器中做修改     * 3,管理添加后,状态不能统一管理;     * 4,管理打开状态下,左滑之后右滑会状态混乱;     * 5,同时向左滑动两个Item;     * 6,管理点击后显示的页面正确,未展示出来的部分展示不对;     * 7,多个条目同时滑动;     */}


如此,按照以上的步骤实现,就能够实现开篇实现的效果。

这个过程出现以下问题,逐一分解解决。


三、出现相关问题修复优化


1,滑动状态不能保持

在Bean类中添加控制变量,赋予默认值。


public class ItemBean implements Serializable {    /**     * 图片索引     */    private int picIndex;    /**     * 描述     */    private String descContent;    public int getPicIndex() {        return picIndex;    }    public void setPicIndex(int picIndex) {        this.picIndex = picIndex;    }    public String getDescContent() {        return descContent;    }    public void setDescContent(String descContent) {        this.descContent = descContent;    }    /**     * 是否打开状态     */    private boolean isOpen = false;    public boolean isOpen() {        return isOpen;    }    public void setOpen(boolean open) {        isOpen = open;    }}

 private boolean isOpen = false;

当实现滑动时,修改该变量的值,就能够保持状态值。


模拟器在录屏时刷新不及时,有点延迟。


2,添加全局的控制管理

顶部添加“管理”按钮,实现整体状态的管理。

注意在实现的时候,修改adpter中的状态时,要同时修改每一个bean类中的状态值。


  /**     * 设置全部打开状态     *     * @param open     */    public void setOpenStatus(boolean open) {        isOpen = open;        if (dataList != null && dataList.size() > 0) {            for (int i = 0; i < dataList.size(); i++) {                dataList.get(i).setOpen(open);            }        }        notifyDataSetChanged();    }

3,注意在监听左滑时,我们加入了清除其他的已经左滑的状态。

在管理状态下,先右滑其中一个再次左滑时,按照以上的逻辑,会将其他的左滑状态全部改回正常状态。添加状态控制,正好在setOpenStatus()中保存了当前正处于的状态。


     @Override            public void onStartOpen(SwipeLayout mSwipeLayout) {                if (!isOpen) {                    for (SwipeLayout layout : openList) {                        layout.close();                    }                    openList.clear();                }            }

4,ListView的多item同时左滑

就出来下面这个么情形:



Activity会引用theme,在theme中添加:

   <!--添加到Application中,所有的Activity都不能多点点击使用-->        <item name="android:windowEnableSplitTouch">false</item>        <item name="android:splitMotionEvents">false</item>

则可以控制,一次只能滑动一个item。将上面的部分添加到Application中,会让全部的Activity都不能多点触碰。


源码传送门



存在感从来不是争取来的!你需要做的,是去锤炼自己的能力,同时找回那个被丢在半路上的自己。

因为你不爱自己。你让自己受了太多太多委屈。你逞强,你倔强,你尽量的照顾别人的感受,却频频的让自己受伤……
你要先爱自己,给她吃,给她喝,给她情书。

你若盛开,蝴蝶自来;你若精彩,天自安排!
你若精彩,老天自有安排!
——做有意义的事,去经营更好的自己


阅读全文
0 0