開源庫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下載!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM