Android SwipeActionAdapter结合Pinnedheaderlistview实现复杂列表的左右滑动操作

  在上一篇博客《Android 使用SwipeActionAdapter开源库实现简单列表的左右滑动操作》里,已经介绍了利用SwipeActionAdapter来左右滑动操作列表; 然,有时候,会要求一些特殊的列表也能够实现左右滑动: 列表滑动过程中,分组标题可以固定在顶部,同时列表支持左右滑动!效果图如下:



  那么该如何实现呢,一开始,我是打算使用SwipeActionAdapter+StickyListView 来做,尝试一番后,发现左右滑动ListView Item时,它的背景(上图滑动时出现的颜色)不显示不显示了,怎么也解决不了,后来在github上也搜索了一番,也发现了这么一个项目https://github.com/he667/StickyListSwipe,同样是想使用SwipeActionAdapter+StickyListView来实现上图的效果,然,该项目也遇到了背景不显示的情况,且没有解决,无奈之下舍弃了该项目,决定使用SwipeActionAdapter+Pinnedheaderlistview,实现了上图所示效果。

  

  关于如何使用SwipeActionAdapter该库,请看我上一篇博客《Android 使用SwipeActionAdapter开源库实现简单列表的左右滑动操作》;至于Pinnedheaderlistview的介绍,可以看下《Android PinnedHeaderListView 详解》这篇博客。

  先熟悉下这两个库的使用,然后在跟着继续学习如何把这两个库融合到一起实现上面的效果!


1. 创建项目,分别导入SwipeActionAdapter和Pinnedheaderlistview两个库,这样方便直接对源码进行修改。


 这里,因为之后修改代码时,SwipeActionAdapter和Pinnedheaderlistview之间也会有些依赖关系,所以我精简了一些,直接把SwipeActionAdapter拷贝到sample模块下,方便修改引用。(到最后下载源码即可看到最重的项目)

Android SwipeActionAdapter结合Pinnedheaderlistview实现复杂列表的左右滑动操作_第1张图片

2. 使用Pinnedheaderlistview,创建头部可悬浮显示的列表

 2.1 列表适配器

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

import bean.HeaderItem;
import za.co.immedia.pinnedheaderlistview.SectionedBaseAdapter;

public class TestSectionedAdapter extends SectionedBaseAdapter {

    List<HeaderItem> headers;

    public TestSectionedAdapter(List<HeaderItem> headers) {
        this.headers = headers;
    }

    @Override
    public Object getItem(int section, int position) {
        return null;
    }

    @Override
    public long getItemId(int section, int position) {
        return 0;
    }

    @Override
    public int getSectionCount() {
        return headers.size();
    }

    @Override
    public int getCountForSection(int section) {
        return headers.get(section).dataItem.size();
    }

    @Override
    public View getItemView(int section, int position, View convertView, ViewGroup parent) {

        LinearLayout layout = null;

        if (convertView == null) {
            LayoutInflater inflator = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            layout = (LinearLayout) inflator.inflate(R.layout.list_item, null);
        } else {
            layout = (LinearLayout) convertView;
        }

//        ((TextView) layout.findViewById(R.id.textItem)).setText("Section " + section + " Item " + position);
        ((TextView) layout.findViewById(R.id.textItem)).setText(headers.get(section).dataItem.get(position).name);

        return layout;
    }

    @Override
    public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {
        LinearLayout layout = null;
        if (convertView == null) {
            LayoutInflater inflator = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            layout = (LinearLayout) inflator.inflate(R.layout.header_item, null);
        } else {
            layout = (LinearLayout) convertView;
        }
//        ((TextView) layout.findViewById(R.id.textItem)).setText("Header for section " + section);
        ((TextView) layout.findViewById(R.id.textItem)).setText(headers.get(section).name);
        return layout;
    }
}
  Adapter继承了SectionedBaseAdapter,并且实现它里面的抽象方法,尤其是getItemView和getSectionHeaderView两个方法,前者用于创建普通列表的布局,后者用于创建悬浮标题的布局。(具体讲解可参看 Android PinnedHeaderListView 详解


  2.2 填充数据,实现基本的悬浮头部列表

public class MainActivity extends Activity {

    List<HeaderItem> headers = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initData();

        initView();
    }

    private void initView() {

        PinnedHeaderListView listView = (PinnedHeaderListView) findViewById(R.id.pinnedListView);

        SectionedBaseAdapter sectionedAdapter = new TestSectionedAdapter(headers);

        listView.setAdapter(sectionedAdapter);
    }

    private void initData() {

        for (int i = 0; i < 5; i++) {

            HeaderItem headerItem = new HeaderItem();
            List<DataItem> datas = new ArrayList<>();

            for (int j = 0; j < 5; j++) {
                DataItem dataItem = new DataItem();
                dataItem.name = " 列表数据 " + j;
                datas.add(dataItem);
            }

            headerItem.name = "标题 " + i;
            headerItem.dataItem = datas;

            headers.add(headerItem);
        }
    }
}

3. 在 Pinnedheaderlistview基础上,扩展SectionedBaseAdapter

 3.1 修改SwipeActionAdapter,让其实现PinnedHeaderListView.PinnedSectionedHeaderAdapter,并实现其中的方法

public class SwipeActionAdapter extends DecoratorAdapter implements
        SwipeActionTouchListener.ActionCallbacks, PinnedHeaderListView.PinnedSectionedHeaderAdapter {

    private ListView mListView;
    private SwipeActionTouchListener mTouchListener;
    protected SwipeActionListener mSwipeActionListener;
    private boolean mFadeOut = false;
    private boolean mFixedBackgrounds = false;
    private boolean mDimBackgrounds = false;
    private float mFarSwipeFraction = 0.5f;
    private float mNormalSwipeFraction = 0.25f;

    protected HashMap<SwipeDirection, Integer> mBackgroundResIds = new HashMap<>();

    private SectionedBaseAdapter listAdapter;

    public SwipeActionAdapter(BaseAdapter baseAdapter, SectionedBaseAdapter listAdapter) {
        super(baseAdapter);
        this.listAdapter = listAdapter;
    }

    @Override
    public View getView(final int position, final View convertView, final ViewGroup parent) {
        SwipeViewGroup output = (SwipeViewGroup) convertView;
        if (output == null) {
            output = new SwipeViewGroup(parent.getContext());
            for (Map.Entry<SwipeDirection, Integer> entry : mBackgroundResIds.entrySet()) {
                output.addBackground(View.inflate(parent.getContext(), entry.getValue(), null), entry.getKey());
            }
            output.setSwipeTouchListener(mTouchListener);
        }

        output.setContentView(super.getView(position, output.getContentView(), output));

//        LogUtils.e(output);
        return output;
    }

    /**
     * SwipeActionTouchListener.ActionCallbacks callback
     * We just link it through to our own interface
     *
     * @param position  the position of the item that was swiped
     * @param direction the direction in which the swipe has happened
     * @return boolean indicating whether the item has actions
     */
    @Override
    public boolean hasActions(int position, SwipeDirection direction) {
        // 这样设置,标题栏就可以不用滑动了
        if (listAdapter != null) {

            if (listAdapter.isSectionHeader(position)) {
                return false;
            }
        }
        return mSwipeActionListener != null && mSwipeActionListener.hasActions(position, direction);
    }

    /**
     * SwipeActionTouchListener.ActionCallbacks callback
     * We just link it through to our own interface
     *
     * @param listView  The originating {@link ListView}.
     * @param position  The position to perform the action on, sorted in descending  order
     *                  for convenience.
     * @param direction The type of swipe that triggered the action
     * @return boolean that indicates whether the list item should be dismissed or shown again.
     */
    @Override
    public boolean onPreAction(ListView listView, int position, SwipeDirection direction) {
        return mSwipeActionListener != null && mSwipeActionListener.shouldDismiss(position, direction);
    }

    /**
     * SwipeActionTouchListener.ActionCallbacks callback
     * We just link it through to our own interface
     *
     * @param listView  The originating {@link ListView}.
     * @param position  The positions to perform the action on, sorted in descending  order
     *                  for convenience.
     * @param direction The type of swipe that triggered the action.
     */
    @Override
    public void onAction(ListView listView, int[] position, SwipeDirection[] direction) {
        if (mSwipeActionListener != null) mSwipeActionListener.onSwipe(position, direction);
    }

    /**
     * Set whether items should have a fadeOut animation
     *
     * @param mFadeOut true makes items fade out with a swipe (opacity -> 0)
     * @return A reference to the current instance so that commands can be chained
     */
    @SuppressWarnings("unused")
    public SwipeActionAdapter setFadeOut(boolean mFadeOut) {
        this.mFadeOut = mFadeOut;
        if (mListView != null) mTouchListener.setFadeOut(mFadeOut);
        return this;
    }

    /**
     * Set whether the backgrounds should be fixed or swipe in from the side
     * The default value for this property is false: backgrounds will swipe in
     *
     * @param fixedBackgrounds true for fixed backgrounds, false for swipe in
     */
    @SuppressWarnings("unused")
    public SwipeActionAdapter setFixedBackgrounds(boolean fixedBackgrounds) {
        this.mFixedBackgrounds = fixedBackgrounds;
        if (mListView != null) mTouchListener.setFixedBackgrounds(fixedBackgrounds);
        return this;
    }

    /**
     * Set whether the backgrounds should be dimmed when in no-trigger zone
     * The default value for this property is false: backgrounds will not dim
     *
     * @param dimBackgrounds true for dimmed backgrounds, false for no opacity change
     */
    @SuppressWarnings("unused")
    public SwipeActionAdapter setDimBackgrounds(boolean dimBackgrounds) {
        this.mDimBackgrounds = dimBackgrounds;
        if (mListView != null) mTouchListener.setDimBackgrounds(dimBackgrounds);
        return this;
    }

    /**
     * Set the fraction of the View Width that needs to be swiped before it is counted as a far swipe
     *
     * @param farSwipeFraction float between 0 and 1
     */
    @SuppressWarnings("unused")
    public SwipeActionAdapter setFarSwipeFraction(float farSwipeFraction) {
        if (farSwipeFraction < 0 || farSwipeFraction > 1) {
            throw new IllegalArgumentException("Must be a float between 0 and 1");
        }
        this.mFarSwipeFraction = farSwipeFraction;
        if (mListView != null) mTouchListener.setFarSwipeFraction(farSwipeFraction);
        return this;
    }

    /**
     * Set the fraction of the View Width that needs to be swiped before it is counted as a normal swipe
     *
     * @param normalSwipeFraction float between 0 and 1
     */
    @SuppressWarnings("unused")
    public SwipeActionAdapter setNormalSwipeFraction(float normalSwipeFraction) {
        if (normalSwipeFraction < 0 || normalSwipeFraction > 1) {
            throw new IllegalArgumentException("Must be a float between 0 and 1");
        }
        this.mNormalSwipeFraction = normalSwipeFraction;
        if (mListView != null) mTouchListener.setNormalSwipeFraction(normalSwipeFraction);
        return this;
    }

    /**
     * We need the ListView to be able to modify it's OnTouchListener
     *
     * @param listView the ListView to which the adapter will be attached
     * @return A reference to the current instance so that commands can be chained
     */
    public SwipeActionAdapter setListView(ListView listView) {

        this.mListView = listView;
        mTouchListener = new SwipeActionTouchListener(listView, this);
        this.mListView.setOnTouchListener(mTouchListener);
        this.mListView.setOnScrollListener(mTouchListener.makeScrollListener());
        this.mListView.setClipChildren(false);
        mTouchListener.setFadeOut(mFadeOut);
        mTouchListener.setDimBackgrounds(mDimBackgrounds);
        mTouchListener.setFixedBackgrounds(mFixedBackgrounds);
        mTouchListener.setNormalSwipeFraction(mNormalSwipeFraction);
        mTouchListener.setFarSwipeFraction(mFarSwipeFraction);
        return this;
    }

    /**
     * Getter that is just here for completeness
     *
     * @return the current ListView
     */
    @SuppressWarnings("unused")
    public AbsListView getListView() {
        return mListView;
    }

    /**
     * Add a background image for a certain callback. The key for the background must be one of the
     * directions from the SwipeDirections class.
     *
     * @param key   the identifier of the callback for which this resource should be shown
     * @param resId the resource Id of the background to add
     * @return A reference to the current instance so that commands can be chained
     */
    public SwipeActionAdapter addBackground(SwipeDirection key, int resId) {
        if (SwipeDirection.getAllDirections().contains(key)) mBackgroundResIds.put(key, resId);
        return this;
    }

    /**
     * Set the listener for swipe events
     *
     * @param mSwipeActionListener class listening to swipe events
     * @return A reference to the current instance so that commands can be chained
     */
    public SwipeActionAdapter setSwipeActionListener(SwipeActionListener mSwipeActionListener) {
        this.mSwipeActionListener = mSwipeActionListener;
        return this;
    }

    @Override
    public boolean isSectionHeader(int position) {
        return listAdapter.isSectionHeader(position);
    }

    @Override
    public int getSectionForPosition(int position) {
        return listAdapter.getSectionForPosition(position);
    }

    @Override
    public int getPositionInSectionForPosition(int position) {
        return listAdapter.getPositionInSectionForPosition(position);
    }

    @Override
    public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {
        return listAdapter.getSectionHeaderView(section, convertView, parent);
    }

    @Override
    public int getSectionHeaderViewType(int section) {
        return listAdapter.getSectionHeaderViewType(section);
    }

    /**
     * Interface that listeners of swipe events should implement
     */
    public interface SwipeActionListener {

        boolean hasActions(int position, SwipeDirection direction);

        boolean shouldDismiss(int position, SwipeDirection direction);

        void onSwipe(int[] position, SwipeDirection[] direction);
    }
}
  注: 在SwipeActionAdapter中,我们把 PinnedHeaderListView.PinnedSectionedHeaderAdapter接口中的方法,完全交给了通过构造方法传递过来的listAdapter进行处理,SwipeActionAdapter只做了一层封装的作用。




源码地址: https://github.com/zuiwuyuan/PinnedHeader_SwipeAction_ListView


你可能感兴趣的:(Android SwipeActionAdapter结合Pinnedheaderlistview实现复杂列表的左右滑动操作)