zoukankan      html  css  js  c++  java
  • 开源库AndroidSwipeLayout分析(一),炫酷ItemView滑动呼出效果


    如需转载请注明博客出处: http://www.cnblogs.com/wondertwo/p/5525671.html


    开源库AndroidSwipeLayout地址请戳: https://github.com/daimajia/AndroidSwipeLayout 开源库作者 @代码家


    实现一个类似手机QQ聊天列表项item侧滑呼出对应的删除、置顶、标为未读等一个或多个操作项的动能,起初的想法很简单,直接拦截掉ViewPager的滑动事件,然后为上层View加置顶的平移动画即可。可是这样代码就会写死,代码可复用性也不高,本着不重复造轮子的原则,在Github发现了AndroidSwipeLayout这个开源库就是做这个功能的,所以就研究了下写点总结。第一篇会具体介绍用法;第二篇会分析这个开源库核心组件SwipeLayout源码o( ̄▽ ̄)d

    AndroidSwipeLayout的用法非常广泛,不仅仅可以用在ListView和GridView上,实现类似手机QQ聊天列表项ItemView的左滑呼出效果,而且还可以用在RecyclerView上,实现样式丰富多彩的瀑布流式效果,比如在本篇博客的第二部分,我实现了一个瀑布流式的异步图片加载功能,就可以通过SwipeLayout为每个Item加上滑动呼出功能操作的效果,想想都觉得很有意思,有兴趣的可以自己亲自去敲一遍,非常简单。本篇博客主要分两个部分来写:

    • 简要说明AndroidSwipeLayout开源库的结构(结合ListView和GridView);
    • 介绍AndroidSwipeLayout在RecyclerView上使用方法;

    第一部分 AndroidSwipeLayout开源库的结构(结合ListView和GridView)

    先看AndroidSwipeLayout的目录结构,如下图:

    回忆一下,在使用ListView和GridView的时候,一般都是简单的三步走:

    • 封装数据源data;
    • 重写Adapter适配器;
    • 创建ListView或者GridView实例,为其设置adapter;

    通过以上三步,数据源和展示层已经完全解耦,数据源和ItemView的适配工作完全交由中间层Adapter来完成,这也体现了经典的MVC模型思想。AndroidSwipeLayout的做法很聪明,直接在Adapter层做文章,我们就来一探究竟!下面贴的是一个ListViewAdapter,代码如下:

    public class ListViewAdapter extends BaseSwipeAdapter {
        private Context mContext;
        private LayoutInflater mInflater;
    
        public ListViewAdapter(Context context) {
            this.mContext = context;
            mInflater = LayoutInflater.from(context);
        }
    
        @Override
        public int getSwipeLayoutResourceId(int i) {
            return R.id.listview_swipe;
        }
    
        /**
         * 重新计算ListView的每个item view的大小
         */
        @Override
        public View generateView(int i, ViewGroup viewGroup) {
            View v = mInflater.inflate(R.layout.listview_item, null);
            SwipeLayout swipeLayout = (SwipeLayout)v.findViewById(getSwipeLayoutResourceId(i));
            swipeLayout.addSwipeListener(new SimpleSwipeListener() {
                @Override
                public void onOpen(SwipeLayout layout) {
    
                }
            });
            swipeLayout.setOnDoubleClickListener(new SwipeLayout.DoubleClickListener() {
                @Override
                public void onDoubleClick(SwipeLayout layout, boolean surface) {
                    Toast.makeText(mContext, "DoubleClick", Toast.LENGTH_SHORT).show();
                }
            });
            v.findViewById(R.id.delete).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(mContext, "click delete", Toast.LENGTH_SHORT).show();
                }
            });
            v.findViewById(R.id.item_button).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext, "click button", Toast.LENGTH_SHORT).show();
                }
            });
            return v;
        }
    
        /**
         * 为ListView的每个item项进行赋值
         */
        @Override
        public void fillValues(int i, View view) {
            TextView text = (TextView) view.findViewById(R.id.item_position);
            Button button = (Button) view.findViewById(R.id.item_button);
            text.setText("第 " + (i+1) + " 个列表项");
            text.setTextColor(Color.RED);
            text.setPadding(40, 0, 20, 0);
            button.setText(R.string.button_str);
        }
    
        @Override
        public int getCount() {
            return 99;
        }
    
        @Override
        public Object getItem(int position) {
            return null;
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    }
    

    ListViewAdapter继承自BaseSwipeAdapter,而BaseSwipeAdapter就是开源库封装好的多个Adapter之一。重写Adapter最重要的任务就是重写getView()方法,然而在上面的代码中,我们并没有发现重写的getView()方法,仔细一看,发现ListViewAdapter中多出了以下3个方法:

    |--  public View generateView(int position, ViewGroup parent) {}
    |--  public void fillValues(int position, View convertView) {}
    |--  public int getSwipeLayoutResourceId(int position) {}
    

    只关注前两个方法,按住Ctrl+左击,跟踪到BaseSwipeAdapter源码中,发现不仅重写了getView()方法,而且generateView()和getSwipeLayoutResourceId()这两个方法都在重写的getView()方法中调用,于是我们把getView()方法单独拿出来分析,源码如下:

    @Override
    public final View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;
        if(v == null){
            v = generateView(position, parent);
        }
        mItemManger.bind(v, position);
        fillValues(position, v);
        return v;
    }
    

    果然是这样!实现的逻辑也很简单,把ItemView的生成过程和ItemView的数据绑定过程拆分为两部分进行。先是调用generateView()方法生成ItemView,这样做的好处是我们可以在generateView()方法只关注View的事件处理逻辑,而不用关心View和数据源的绑定,这个生成的View就是我们getView()方法最后要返回的View;而绑定ItemView和数据源的工作就交给fillValues()方法了。

    到此你会发现,要想实现ItemView的侧滑呼出效果,AndroidSwipeLayout库关注的只是中间层Adapter而已,并且对Android sdk提供的几种常用的Adapter都进行了封装,对应关系如下:

    • ArraySwipeAdapter对应ArrayAdapter;
    • BaseSwipeAdapter对应BaseAdapter;
    • CursorSwipeAdapter对应CursorAdapter;
    • SimpleCursorSwipeAdapter对应SimpleCursorAdapter;
    • RecyclerSwipeAdapter对应CyclerAdapter;

    到这里,使用SwipeLayout就很简单了,只需要在ItemView的根布局嵌套一个SwipeLayout控件就可以了,例如看一下ListView的布局文件listView_item.xml如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <com.daimajia.swipe.SwipeLayout
        xmlns:swipe="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/listview_swipe"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        swipe:leftEdgeSwipeOffset="0dp"
        swipe:rightEdgeSwipeOffset="0dp">
    
        <!--BottomViews-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:background="#FF5534"
            android:gravity="center"
            android:tag="Bottom3"
            android:weightSum="10">
            <ImageView
                android:id="@+id/trash"
                android:layout_width="27dp"
                android:layout_height="30dp"
                android:layout_weight="1"
                android:src="@drawable/trash" />
            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="5"
                android:text="Delete Item?"
                android:textColor="#fff"
                android:textSize="17sp" />
            <Button
                android:id="@+id/delete"
                android:layout_width="0dp"
                android:layout_height="40dp"
                android:layout_weight="4"
                android:background="@drawable/white"
                android:text="Yes,Delete"
                android:textColor="#FF5534" />
        </LinearLayout>
    
        <!--SurfaceViews-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/item_selector"
            android:padding="10dp">
    
            <TextView
                android:id="@+id/item_position"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
    
            <Button
                android:id="@+id/item_button"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:tag="Hover"
                android:text="@string/button_str" />
        </LinearLayout>
    </com.daimajia.swipe.SwipeLayout>
    

    在com.daimajia.swipe.SwipeLayout控件内部添加了两个LinearLayout,上面那个LinearLayout是BottomViews(底层View),下面那个LinearLayout是SurfaceViews(表层View);当SurfaceView被滑开,BottomView就会展示出来,示意图如下:


    第二部分 AndroidSwipeLayout在RecyclerView上的用法

    其实熟悉RecyclerView的同学,肯定都不愿意再滚回去用ListView和GridView了。至于RecyclerView怎么用?好在哪?网上随便都能搜出一堆,这里不讨论!我用RecyclerView做了一个异步加载网络图片的瀑布流小demo,用AndroidSwipeLayout为每个Item添加了左滑呼出功能菜单的效果,详细请看下图:

    项目开始之前,需要导入AndroidSwipeLayout库文件,可以去Github下载;也可以选择下载jar包,把下载好的jar包放在项目的额libs目录下,并右键add as a library!这两种方式选其一即可。库导入完成后,项目的目录结构如下图:

    第一步需要将SwipeLayout组件设置给ItemView的根布局,也就是我们这里的recyclerview_item.xml布局文件,代码如下:

    <com.daimajia.swipe.SwipeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:swipe="http://schemas.android.com/apk/res-auto"
        android:id="@+id/itemview_swipe"
        swipe:leftEdgeSwipeOffset="0dp"
        swipe:rightEdgeSwipeOffset="0dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <!--BottomViews-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:orientation="vertical"
            android:background="#FF5534"
            android:gravity="center"
            android:tag="Bottom3"
            android:weightSum="10">
            <ImageView
                android:id="@+id/bottom_trash"
                android:layout_width="27dp"
                android:layout_height="38dp"
                android:layout_gravity="center_horizontal"
                android:paddingBottom="5dp"
                android:src="@drawable/trash" />
            <Button
                android:id="@+id/bottom_delete"
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_gravity="center_horizontal"
                android:paddingLeft="5dp"
                android:paddingRight="5dp"
                android:background="#ffffff"
                android:text="Delete Item ?"
                android:textSize="18sp"
                android:textColor="#FF5534" />
        </LinearLayout>
    
        <!--SurfaceViews-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <ImageView
                android:id="@+id/surface_user_image"
                android:layout_width="100dp"
                android:layout_height="100dp"/>
        </LinearLayout>
    
    </com.daimajia.swipe.SwipeLayout>
    

    可以清楚的看到,SwipeLayout组件装了两个LinearLayout,上下两个LinearLayout分别代表BottomViews和SurfaceViews,当SurfaceViews滑开时,就会看到底层的BottomViews,这样理解起来非常形象。然后最最关键的,来看我们的适配器代码,StaggeredAdapter.java代码如下所示:

    /**
     * StaggeredAdapter 瀑布流式的RecyclerView数据源适配器
     *
     * Created by wondertwo on 2016/5/19.
     */
    public class StaggeredAdapter extends RecyclerView.Adapter<StaggeredAdapter.MyViewHolder> {
    
        private Context mContext;
        private LayoutInflater mInflater;
        private String[] mDatas;
        private AsyncImageLoader mImageLoader;
        private List<Integer> mHeights;
    
        public StaggeredAdapter(Context context, String[] data) {
            this.mContext = context;
            mInflater = LayoutInflater.from(context);
            mDatas = data;
            mImageLoader = new AsyncImageLoader(mContext);
    
            // 随机生成item的高度
            mHeights = new ArrayList<>();
            for (String mData : mDatas) {
                mHeights.add((int) (100 + Math.random() * 300));
            }
        }
    
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new MyViewHolder(mInflater.inflate(R.layout.recyclerview_item, parent, false));
        }
    
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            // 设置 itemView 高度
            ViewGroup.LayoutParams lp = holder.iv.getLayoutParams();
            lp.height = mHeights.get(position);
            holder.iv.setLayoutParams(lp);
    
            final String imgUrl = mDatas[position];
            // 给 ImageView 设置一个 tag
            holder.iv.setTag(imgUrl);
            // 给 ImageView 预设一个图片
            holder.iv.setImageResource(R.drawable.ic_launcher);
    
            if (!TextUtils.isEmpty(imgUrl)) {
                Bitmap bitmap = mImageLoader.loadImage(holder.iv, imgUrl);
                if (bitmap != null) {
                    holder.iv.setImageBitmap(bitmap);
                }
            }
        }
    
        @Override
        public int getItemCount() {
            return mDatas.length;
        }
    
        // ViewHolder
        class MyViewHolder extends RecyclerView.ViewHolder {
            ImageView iv;
            public MyViewHolder(View view) {
                super(view);
                iv = (ImageView) view.findViewById(R.id.surface_user_image);
            }
        }
    }
    
    1. StaggeredAdapter就是实现瀑布流式加载效果,继承自RecyclerView.Adapter;包含一个内部类MyViewHolder,继承自RecyclerView.ViewHolder。除此之外,需要关心的就只有onBindViewHolder()方法了。
    2. onBindViewHolder()方法中,先把我们随机生成的高度值设置给ItemView;然后为每个ItemView设置一个TAG,这样可以避免出现图片加载错乱的问题;最后把从网络上加载下来的图片设置给ItemView。

    装备工作完成后,在MainActivity中把StaggeredAdapter设置给RecyclerView实例即可,这里注释写的比较详细,不熟悉RecyclerView的同学,完全可以按以下几步熟练地运用RecyclerView了,代码如下所示:

        // 第一步:拿到RecyclerView对象
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        // 第二步:设置布局管理器
        mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2,
                StaggeredGridLayoutManager.VERTICAL));
        // 第三步:设置adapter适配器、点击监听
        mAdapter = new StaggeredAdapter(MainActivity.this, images);
        mRecyclerView.setAdapter(mAdapter);
        // 第四步:设置item装饰器ItemDecoration,或者item动画ItemAnimation
        mRecyclerView.addItemDecoration(new DividerGridItemDecoration(this));
    

    就是这么简单!只有几行代码而已。另外,项目源码已上传Github,需要源码的同学请在我的Github下载!

  • 相关阅读:
    Python语言解析xml文件
    运行manage.py db shell出错
    ImportError: No module named win32com.client
    ImportError: No module named urllib2
    《演讲之禅》 读书笔记
    no such table: django_admin_log
    2 策略模式(2)
    1 简单工厂模式
    2 策略模式(1)
    无言
  • 原文地址:https://www.cnblogs.com/wondertwo/p/5525671.html
Copyright © 2011-2022 走看看