Android自定义View之PinnedHeaderListView

PS:做android已经有一年的时间了,在外包干了一年,所以也做了将近10多个app了,各种类型的都有,虽然做了很多app,也实现了很多功能,只能说你现在给我一个需求,我能自己查查资料倒腾倒腾我能把它做出来,所以我还是停留在会用的阶段,还没怎么深入的研究一个东西,还停留在初级程序员的道路,准备向中级程序员进发了,听了许多大牛们的成长之路,就是要不断的总结,光看没用,哪怕是照着别人的代码敲一遍,对于你来说也会有很多收获的,于是我开始写博客了,见证下自己的成长过程!大牛勿喷!^~^
本次研究的东西是PinnedHeaderListView,也就是头部悬浮,并且不断的更新,想必大家已经在很多地方看到过了,
![运行效果图如下]
Android自定义View之PinnedHeaderListView_第1张图片
实现的方式
(一)可以用一个FramLayout底下放一个ListView,上面覆盖一个需要悬浮的布局,通过不断的监听ListView滑动的firstVisibleItem来判断是否需要替换
(二)第一种方式想必大家都会很容易的实现,但是今天我们采用的是继承一个ListView的方式自定义一个叫PinnedListView的方式,在ListView的顶部绘制一个布局,通过监听firstVisibleItem来判断是否需要替换。

布局文件layout/activity_custom_title_listview_section.xml(HeaderView)如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="25dip" android:background="#c3c3c3">

    <TextView android:id="@+id/header_text" android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="section" android:textSize="14sp" android:textStyle="bold" android:textColor="#ffffff" android:gravity="center_vertical" />
</RelativeLayout>

布局文件layout/activity_custom_title_listview_section.xml(ListView的item文件)如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#ffffffff" android:clickable="true" >

    <RelativeLayout android:id="@+id/header_parent" android:layout_width="match_parent" android:layout_height="25dip" >

        <TextView android:id="@+id/header" android:layout_width="fill_parent" android:layout_height="fill_parent" android:textSize="14sp" android:textStyle="bold" android:textColor="#ffffff" android:gravity="center_vertical" android:background="#c3c3c3" android:text="header" />
    </RelativeLayout>

    <LinearLayout  android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="40dip" >

        <TextView  android:id="@+id/example_text_view" android:text="TextView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="14sp" >
        </TextView>

    </LinearLayout>
</LinearLayout>

开工!根据自定义View的步骤
第一步:首先我们写一个类继承ListView,覆盖三个构造方法

public class PinnedHeaderListView extends ListView{
    public PinnedHeaderListView(Context context) {
        this(context, null);
    }

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

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

第二步:我们需要拿到我们要绘制的HeaderView的宽高,所以我们重写了一下onMeasure方法,在这里测量一下子view,只有测量过后子view通过调用自己的onMeasure方法告诉父控件宽高,我们才能拿到HeaderView的宽高。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取HeaderView的高度
        if(mHeaderView!=null){
            measureChild(mHeaderView,widthMeasureSpec,heightMeasureSpec);
            mHeaderViewWidth=mHeaderView.getMeasuredWidth();
            mHeaderViewHeight=mHeaderView.getMeasuredHeight();
        }
    }

第三步:重写dispatchDraw方法,绘制HeaderVeiw,因为定义一个ViewGroup的时候一般如果要绘制的话都是重写dispatchDraw方法,而不去重写onDraw方法,因为只有设置了背景Viewgroup才会调用onDraw方法,而dispatchDraw不管怎样都会调用,具体为什么看源码^~^

 @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if(mHeaderViewVisible){
        /** * @param canvas The canvas on which to draw the child * @param child Who to draw * @param drawingTime The time at which draw is occurring * @return True if an invalidate() was issued */
            drawChild(canvas, mHeaderView, getDrawingTime());
          // Log.e("PinnerListView", "dispatchDraw: childCount---->"+getChildCount() );
        }
    }

第五步:就是重写onLayout摆放我们的HeaderView了,我们把HeaderView放在了控件的最顶端,(如果我们的ListView加了自己的真正意义上的HeaderView的话那么MyHeaderView摆放的位置的top需要加上自己的HeaderView的高度了)

 @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);
 //摆放完后计算一下HeaderView configureHeaderView(getFirstVisiblePosition());
            Log.i("TAG", "layout");
        }
    }

第六步:也是我们本次研究的最重要的一步了,计算HeaderView显示的位置,判断是否需要更新和替换
在此之前,我们创建一个接口PinnedHeaderAdapter,然后自定义一个Adapter(PinnedAdapter),让PinnedAdapter去继承PinnedHeaderAdapter,让我们PinnedListView去回调,从而通知Adapter我们是否需要更新和替换Header

 public interface PinnedHeaderAdapter{
         //Header消失
        int PINNED_HEADER_GONE=0;
        //Header出现
        int PINNED_HEADER_VISIBLE=1;
        //上拉下拉中
        int PINNED_HEADER_PUSHED_UP=2;

        /** * 获取当前header的状态 * @param position * @return */
        int getPinnedHeaderState(int position);

        /** * 当需要变换header的时候调用 * @param header * @param position */
        void configurePinnerHeader(View header,int position);
    }

PinnedAdapte的代码如下(继承了PinnedHeaderAdapter跟OnScrollListener ):


/** * Author:Yqy * Date:2016-08-04 * Desc: * Company:cisetech */
public class PinnerAdpater extends BaseAdapter implements PinnedHeaderListView.PinnedHeaderAdapter, AbsListView.OnScrollListener {
    private List<User> mDatas;
    private Context context;
    public PinnerAdpater(Context context,List<User>mDatas){
        this.context=context;
        this.mDatas=mDatas;
    }
    @Override
    public int getCount() {
        return mDatas.size();
    }

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

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        User user= (User) getItem(position);
        if(convertView==null){
            convertView=View.inflate(context, R.layout.activity_custom_title_listview_section_item,null);
            convertView.setTag(""+position);
        }
        TextView title= (TextView) convertView.findViewById(R.id.header);
        TextView content= (TextView) convertView.findViewById(R.id.example_text_view);
        title.setText(user.getName());
        content.setText(user.getNumber());
        return convertView;
    }

    @Override
    public int getPinnedHeaderState(int position) {
        return PinnedHeaderListView.PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP;
    }
    private int lastItem;
    @Override
    public void configurePinnerHeader(View header, int position) {
        Log.e("PinnerAdapter----->", "configurePinnerHeader: "+position);
        if(lastItem!=position){
            //notifyDataSetChanged();
        }
        TextView text= (TextView) header.findViewById(R.id.header_text);
        text.setText(mDatas.get(position).getName());
        lastItem=position;
    }

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

    }
    //不断监听ListView的scroll滑动的距离开始计算
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    //调用PinnedHeaderListView的公共方法configureHeaderView
        if (view instanceof PinnedHeaderListView) {
            ((PinnedHeaderListView) view).configureHeaderView(firstVisibleItem);
        }
    }
}

重写ListView的setAdapter方法,因为我们的PinnedAdapte继承了PinnedHeaderAdapter,所以我们在这里获取的Adapter强转成PinnedHeaderAdapter获取实例

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

好了,万事具备了,来到我们最关键的configureHeaderView方法了

 public void configureHeaderView(int position) {
        if (mHeaderView == null) {
            return;
        }
        //调用Adapter的getPinnedHeaderState获取我们提前定义好的state,
        int state = mAdapter.getPinnedHeaderState(position);
        switch (state) {
            case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
                mHeaderView.setVisibility(View.GONE);
                break;
            }
            case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
                mHeaderView.setVisibility(View.VISIBLE);
                break;
            }
            /** *关键代码,当返回的state为滑动时,开始计算 */
            case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
                View firstView = getChildAt(0);//获取ListView的第一个view
                int bottom = firstView.getBottom();//获取底部高度
                int headerHeight = mHeaderView.getHeight();//获取mHeaderView的高度
                /** * 当第一个view的bottom<headerHeight的时候也就证明此时的headerView应该更新为当前position的 * 内容,当mHeaderView.getTop() != y的时候 * 开始上移mHeaderView直到写一个view替换当前的headerView, * 如果实在不懂,可以像我一样打个Log运行看一遍结果就知道了^_^! */
                int y;
                if (bottom < headerHeight) {
                    y = (bottom - headerHeight);
                } else {
                    y = 0;
                }
                mAdapter.configurePinnerHeader(mHeaderView, position);
                Log.e("PinnerListView", "bottom---->" + bottom + " headerHeight--->"
                        + headerHeight + " top-->" + mHeaderView.getTop()+" y-->"+y);
                if (mHeaderView.getTop() != y) {
                    mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight
                            + y);
                }
                mHeaderViewVisible = true;
                break;
            }
        }
    }

大功告成,测试一下我们的结果:

public class User {
    private String name;
    private String number;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }
}


public class Test2Activity extends AppCompatActivity {
    private PinnedHeaderListView2 mListView;
    private PinnerAdapter2 mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test2);
        mListView= (PinnedHeaderListView2) findViewById(R.id.id_pinned_listview);
        initData();
    }
    private List<User> datas=new ArrayList<User>();
    private void initData() {
        for (int i = 0; i <50 ; i++) {
            User user=new User();
            user.setName("name-"+i);
            user.setNumber("100" + i);
            datas.add(user);
        }
        mAdapter=new PinnerAdapter2(this,datas);
        mListView.setAdapter(mAdapter);
        mListView.setOnScrollListener(mAdapter);

        mListView.setIsShowHeader(true);
        mListView.setmHeaderView(getLayoutInflater().inflate(R.layout.activity_custom_title_listview_section, mListView, false));
    }
}

最后附上PinnedHeaderListView跟PinnedAdapter的全部代码:

package com.cisetech.customer.customer.Animation;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;

/** * Author:Yqy * Date:2016-08-04 * Desc: * Company:cisetech */
public class PinnedHeaderListView extends ListView{
    public interface PinnedHeaderAdapter{
        int PINNED_HEADER_GONE=0;
        int PINNED_HEADER_VISIBLE=1;
        int PINNED_HEADER_PUSHED_UP=2;

        /** * 获取当前header的状态 * @param position * @return */
        int getPinnedHeaderState(int position);

        /** * 当需要变换header的时候调用 * @param header * @param position */
        void configurePinnerHeader(View header,int position);
    }
    private static final int MAX_ALPHA=255;
    private PinnedHeaderAdapter mAdapter;
    /**当前HeadView*/
    private View mHeaderView;
    private boolean mHeaderViewVisible;//headerView是否可见
    private int mHeaderViewWidth;//headView的宽度
    private int mHeaderViewHeight;//headView的高度

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

    public void setmHeaderView(View mHeaderView) {
        this.mHeaderView = mHeaderView;
        if (mHeaderView != null) {
            setFadingEdgeLength(0);
        }
    }

    public void setmHeaderViewVisible(boolean mHeaderViewVisible) {
        this.mHeaderViewVisible = mHeaderViewVisible;
    }

    public PinnedHeaderListView(Context context) {
        this(context, null);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取HeaderView的高度
        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());
            Log.i("TAG", "layout");
        }
    }

    public void configureHeaderView(int position) {
        if (mHeaderView == null) {
            return;
        }
        //调用Adapter的getPinnedHeaderState获取我们提前定义好的state,
        int state = mAdapter.getPinnedHeaderState(position);
        switch (state) {
            case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
                mHeaderView.setVisibility(View.GONE);
                break;
            }
            case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
                mHeaderView.setVisibility(View.VISIBLE);
                break;
            }
            /** *关键代码,当返回的state为滑动时,开始计算 */
            case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
                View firstView = getChildAt(0);//获取ListView的第一个view
                int bottom = firstView.getBottom();//获取底部高度
                int headerHeight = mHeaderView.getHeight();//获取mHeaderView的高度
                /** * 当第一个view的bottom<headerHeight的时候也就证明此时的headerView应该更新为当前position的 * 内容,当mHeaderView.getTop() != y的时候 * 开始上移mHeaderView直到写一个view替换当前的headerView, * 如果实在不懂,可以像我一样打个Log运行看一遍结果就知道了^_^! */
                int y;
                if (bottom < headerHeight) {
                    y = (bottom - headerHeight);
                } else {
                    y = 0;
                }
                mAdapter.configurePinnerHeader(mHeaderView, position);
                Log.e("PinnerListView", "bottom---->" + bottom + " headerHeight--->"
                        + headerHeight + " top-->" + mHeaderView.getTop()+" y-->"+y);
                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());
          // Log.e("PinnerListView", "dispatchDraw: childCount---->"+getChildCount() );
        }
    }
}
package com.cisetech.customer.customer.Animation;

import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.cisetech.customer.customer.R;

import java.util.List;

/** * Author:Yqy * Date:2016-08-04 * Desc: * Company:cisetech */
public class PinnerAdpater extends BaseAdapter implements PinnedHeaderListView.PinnedHeaderAdapter, AbsListView.OnScrollListener {
    private List<User> mDatas;
    private Context context;
    public PinnerAdpater(Context context,List<User>mDatas){
        this.context=context;
        this.mDatas=mDatas;
    }
    @Override
    public int getCount() {
        return mDatas.size();
    }

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

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        User user= (User) getItem(position);
        if(convertView==null){
            convertView=View.inflate(context, R.layout.activity_custom_title_listview_section_item,null);
            convertView.setTag(""+position);
        }
        TextView title= (TextView) convertView.findViewById(R.id.header);
        TextView content= (TextView) convertView.findViewById(R.id.example_text_view);
        title.setText(user.getName());
        content.setText(user.getNumber());
        return convertView;
    }

    @Override
    public int getPinnedHeaderState(int position) {
        return PinnedHeaderListView.PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP;
    }
    private int lastItem;
    @Override
    public void configurePinnerHeader(View header, int position) {
        Log.e("PinnerAdapter----->", "configurePinnerHeader: "+position);
        if(lastItem!=position){
            //notifyDataSetChanged();
        }
        TextView text= (TextView) header.findViewById(R.id.header_text);
        text.setText(mDatas.get(position).getName());
        lastItem=position;
    }

    @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);
        }
    }
}

你可能感兴趣的:(Android自定义View之PinnedHeaderListView)