仿StickyListHeaders 实现listview的header滑动效果(类似QQ好友列表)

简单模仿StickyListHeaders,给listview内容设置header,随着滚动,header有推出,推入的效果。

先上StickyListHeaders的效果图:

这是StickyListHeaders  的效果图,有点小问题哎,顶部的headerview显示不全。


这是我接下来要弄的仿制品的效果图

效果还行的吧



实现原理分析:

listview通过adpater(重写getView())来给内容设置两种类型的item,一类item是蓝色这种"移动标题",一类是白色这种内容性item。为了区分,我简单做了数据结构的处理,adapter传入的不仅是list<String>,还要传入indexHeaderView[]用来跟踪"移动标题"的位置。                             其次是要实现一个固定在listview最上面的headerview即可了,这个headerview需要和"移动标题"一样的布局。因为每当"移动标题"移动到headerview下面或者上面的时候,那么headerview只要跟着一起移动,然后在移出的时候再显示出来,并修改headerview的文字,这样就覆盖在"移动标题"的上面,给人感觉好像被推出去了一样,其实显示在最上方的还是headerview,大致思路是这样,接下来说说代码。(都有很多注释了的,我再简单说下)

我的实现方式没有StickyListHeaders方式复杂,但是复用性很差。我稍微看了下StickyListHeaders的实现源码,继承frameLayout。

/**
 * Even though this is a FrameLayout subclass we still consider it a ListView.
 * This is because of 2 reasons:
 *   1. It acts like as ListView.
 *   2. It used to be a ListView subclass and refactoring the name would cause compatibility errors.
 *
 * @author Emil Sjölander
 */
public class StickyListHeadersListView extends FrameLayout {

    public interface OnHeaderClickListener {
        void onHeaderClick(StickyListHeadersListView l, View header,
                                  int itemPosition, long headerId, boolean currentlySticky);
    }

我的是通过在PinnedHeaderListView的基础上修改而来,那么先来解释下PinnedHeaderListView这个东西。这个listview是一个头部固定带有headerview,并已经实现headerview的移出,移入的代码。

/**
 * A ListView that maintains a header pinned at the top of the list. The
 * pinned header can be pushed up and dissolved as needed.
 */
public class PinnedHeaderListView extends ListView {

    /**
     * Adapter interface.  The list adapter must implement this interface.
     */
    public interface PinnedHeaderAdapter {

        /**
         * Pinned header state: don't show the header.
         */
        public static final int PINNED_HEADER_GONE = 0;

        /**
         * Pinned header state: show the header at the top of the list.
         */
        public static final int PINNED_HEADER_VISIBLE = 1;

        /**
         * Pinned header state: show the header. If the header extends beyond
         * the bottom of the first shown element, push it up and clip.
         */
        public static final int PINNED_HEADER_PUSHED_UP = 2;

        /**
         * Computes the desired state of the pinned header for the given
         * position of the first visible list item. Allowed return values are
         * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or
         * {@link #PINNED_HEADER_PUSHED_UP}.
         */
        int getPinnedHeaderState(int position);

        /**
         * Configures the pinned header view to match the first visible list item.
         *
         * @param header   pinned header view.
         * @param position position of the first visible list item.
         * @param alpha    fading of the header view, between 0 and 255.
         */
        void configurePinnedHeader(View header, int position, int alpha);
    }

    private static final int MAX_ALPHA = 255;

    private PinnedHeaderAdapter mAdapter;
    private View mHeaderView;
    private boolean mHeaderViewVisible;

    private int mHeaderViewWidth;

    private int mHeaderViewHeight;

    public PinnedHeaderListView(Context context) {
        super(context);
    }

    public PinnedHeaderListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setPinnedHeaderView(View view) {
        mHeaderView = view;

        // Disable vertical fading when the pinned header is present
        // TODO change ListView to allow separate measures for top and bottom fading edge;
        // in this particular case we would like to disable the top, but not the bottom edge.
        if (mHeaderView != null) {
            setFadingEdgeLength(0);
        }
        requestLayout();
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        super.setAdapter(adapter);
        mAdapter = (PinnedHeaderAdapter) adapter;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mHeaderView != null) {
            measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
            mHeaderViewWidth = mHeaderView.getMeasuredWidth();
            mHeaderViewHeight = mHeaderView.getMeasuredHeight();
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mHeaderView != null) {
            mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
            configureHeaderView(getFirstVisiblePosition());
        }
    }

    /**
     * 函数名叫"配置headerview"
     * 其实就是根据三个状态对headerview(最上面的那个东西)做显示,消失,移动的处理
     * @param position listview最上面item的position
     */
    public void configureHeaderView(int position) {
        if (mHeaderView == null) {
            return;
        }

        int state = mAdapter.getPinnedHeaderState(position);
        switch (state) {
            case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
                mHeaderViewVisible = false;
                break;
            }

            case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
                mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA);
                if (mHeaderView.getTop() != 0) {
                    mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
                }
                mHeaderViewVisible = true;
                break;
            }

            case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
                View firstView = getChildAt(0);  
                int bottom = firstView.getBottom();
                int itemHeight = firstView.getHeight();
                int headerHeight = mHeaderView.getHeight();
                int y;
                int alpha;
                if (bottom < headerHeight) {
                    y = (bottom - headerHeight);
                    alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
                } else {
                    y = 0;
                    alpha = MAX_ALPHA;
                }
                mAdapter.configurePinnedHeader(mHeaderView, position, alpha);
                if (mHeaderView.getTop() != y) {
                    mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y);
                }
                mHeaderViewVisible = true;
                break;
            }
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mHeaderViewVisible) {
            drawChild(canvas, mHeaderView, getDrawingTime());
        }
    }

}


其中解释两个方法的作用:

1.setPinnedHeaderView(View view),使用来将你写的headerview布局传入进来,所以初始化后就需要设置该方法,否则headerview为null,那么就不会显示了

2.configureHeaderView(int position) 这个方法是用来改变headerview的状态的,在onScroll监听方法里调用该方法,传入的position是listview的firstVisibleItem



之前说过,我们需要对adapter做处理,让他显示不同的item,接下来看下adapter的代码:

public class MyAdapter extends BaseAdapter implements
        PinnedHeaderListView.PinnedHeaderAdapter, AbsListView.OnScrollListener{

    private Context context;
    private List<String>list; //所有内容(包括移动标题的内容)都在这里
    private int indexSubHeader[]; //移动标题的position

    private View subHeaderView;
    private View contentView;

    public MyAdapter(Context context, List<String> list, int[] indexSubHeader){
        this.context = context;
        this.list = list;
        this.indexSubHeader = indexSubHeader;
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int position) {
        return list.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ContentViewHolder contentViewHolder = null;
        SubHeaderViewHolder subHeaderViewHolder = null;
        ViewHolder viewHolder = null;
        if(contentView != null){
            viewHolder = (ViewHolder)contentView.getTag();
        }
        if(isSubHeaderViewPosition(position)){
            //为了防止两种类型的布局互相复用,导致错乱,在这里加个类型判断。
            if(convertView == null || !(viewHolder instanceof SubHeaderViewHolder)) {
                subHeaderViewHolder = new SubHeaderViewHolder();
                convertView = LayoutInflater.from(context).inflate(R.layout.listview_title_item, parent, false);
                subHeaderViewHolder.tvTitle = (TextView) convertView.findViewById(R.id.listview_item_tv_title);
                convertView.setTag(subHeaderViewHolder);
            } else {
                subHeaderViewHolder = (SubHeaderViewHolder)convertView.getTag();
            }
            subHeaderViewHolder.tvTitle.setText(list.get(position));
        } else {
            if(convertView == null || !(viewHolder instanceof ContentViewHolder)) {
                contentViewHolder = new ContentViewHolder();
                convertView = LayoutInflater.from(context).inflate(R.layout.listview_item, parent, false);
                contentViewHolder.tvContent = (TextView) convertView.findViewById(R.id.listview_item_tv_content);
                convertView.setTag(contentViewHolder);
            } else {
                contentViewHolder = (ContentViewHolder)convertView.getTag();
            }
            contentViewHolder.tvContent.setText(list.get(position));
        }
        return convertView;
    }

    /**
     * 判断该position是否是标题的position
     * @param position
     * @return
     */
    public boolean isSubHeaderViewPosition(int position){
        for(int i=0; i<indexSubHeader.length; i++){
            if(position == indexSubHeader[i]) {
                return true;
            }
        }
        return false;
    }

    //通过position判断该position属于哪个组
    private int getCurrentSubHeaderIndex(int position){
        for(int i=indexSubHeader.length-1; i>=0; i--){
            if(position >= indexSubHeader[i])
                return i;
        }
        return -1;
    }


    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (view instanceof PinnedHeaderListView) {
            ((PinnedHeaderListView) view).configureHeaderView(firstVisibleItem);
        }
    }

    //通过position,来判断headerview的状态
    @Override
    public int getPinnedHeaderState(int position) {
        if (position < 0) {
            return PINNED_HEADER_GONE;
        }
        int currentSubHeaderIndex = getCurrentSubHeaderIndex(position);
        int nextSubHeaderIndex = currentSubHeaderIndex + 1;
        if (nextSubHeaderIndex >= 0
                && nextSubHeaderIndex<indexSubHeader.length
                && indexSubHeader[nextSubHeaderIndex]-1 == position) {
            return PINNED_HEADER_PUSHED_UP;
        }
        return PINNED_HEADER_VISIBLE;
    }

    //改变headerview的内容信息
    @Override
    public void configurePinnedHeader(View header, int position, int alpha) {
        int subHeaderIndex = getCurrentSubHeaderIndex(position);

        if(subHeaderIndex >= 0 && subHeaderIndex < indexSubHeader.length) {
            System.out.println(list.get(indexSubHeader[subHeaderIndex]));
            ((TextView) header.findViewById(R.id.listview_item_tv_title)).setText(list.get(indexSubHeader[subHeaderIndex]));
        }
    }

    class ViewHolder{

    }

    class ContentViewHolder extends ViewHolder{
        TextView tvContent;
    }

    class SubHeaderViewHolder extends ViewHolder{
        TextView tvTitle;
    }
}

代码不多,注释也有,不多费口舌了


接下来看下Acitivity里面调用

private PinnedHeaderListView pListView;
    private MyAdapter myAdapter;
    private int subHeaderIndex[];

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

        initListView();
    }

    private void initListView() {
        pListView = (PinnedHeaderListView)super.findViewById(R.id.activity_main_plistview);

        subHeaderIndex = new int[]{0, 3, 8, 17, 28};
        List<String> list = getList(subHeaderIndex);

        myAdapter = new MyAdapter(this, list, subHeaderIndex);
        View headerView = LayoutInflater.from(this).inflate(R.layout.listview_title_item, pListView, false);

        pListView.setPinnedHeaderView(headerView);
        pListView.setAdapter(myAdapter);
        pListView.setOnScrollListener(myAdapter);
        pListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (!myAdapter.isSubHeaderViewPosition(position)){
                    Toast.makeText(MainActivity.this, "this position = " + (position+1), Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private List<String> getList(int subHeaderIndex[]) {

        final int size = 40;
        List<String>list = new ArrayList<String>();
        int count = 0;
        for(int i=0; i<size; i++){
            if(count<subHeaderIndex.length && i == subHeaderIndex[count]){
                if(count == subHeaderIndex.length-1)
                    list.add("there have " + (size - subHeaderIndex[count]) + " items of listview");
                else
                    list.add("there have " + (subHeaderIndex[count + 1] - subHeaderIndex[count]-1) + " items of listview");
                count++;
            } else {
                list.add("position = " + (i+1));
            }
        }
        return list;
    }



好啦,完成。收工回家...................

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值