Android自定义View之PinnedHeaderListView
2016-08-07 22:04
274 查看
PS:做android已经有一年的时间了,在外包干了一年,所以也做了将近10多个app了,各种类型的都有,虽然做了很多app,也实现了很多功能,只能说你现在给我一个需求,我能自己查查资料倒腾倒腾我能把它做出来,所以我还是停留在会用的阶段,还没怎么深入的研究一个东西,还停留在初级程序员的道路,准备向中级程序员进发了,听了许多大牛们的成长之路,就是要不断的总结,光看没用,哪怕是照着别人的代码敲一遍,对于你来说也会有很多收获的,于是我开始写博客了,见证下自己的成长过程!大牛勿喷!^~^
本次研究的东西是PinnedHeaderListView,也就是头部悬浮,并且不断的更新,想必大家已经在很多地方看到过了,
![运行效果图如下]
实现的方式
(一)可以用一个FramLayout底下放一个ListView,上面覆盖一个需要悬浮的布局,通过不断的监听ListView滑动的firstVisibleItem来判断是否需要替换
(二)第一种方式想必大家都会很容易的实现,但是今天我们采用的是继承一个ListView的方式自定义一个叫PinnedListView的方式,在ListView的顶部绘制一个布局,通过监听firstVisibleItem来判断是否需要替换。
布局文件layout/activity_custom_title_listview_section.xml(HeaderView)如下:
布局文件layout/activity_custom_title_listview_section.xml(ListView的item文件)如下:
开工!根据自定义View的步骤
第一步:首先我们写一个类继承ListView,覆盖三个构造方法
第二步:我们需要拿到我们要绘制的HeaderView的宽高,所以我们重写了一下onMeasure方法,在这里测量一下子view,只有测量过后子view通过调用自己的onMeasure方法告诉父控件宽高,我们才能拿到HeaderView的宽高。
第三步:重写dispatchDraw方法,绘制HeaderVeiw,因为定义一个ViewGroup的时候一般如果要绘制的话都是重写dispatchDraw方法,而不去重写onDraw方法,因为只有设置了背景Viewgroup才会调用onDraw方法,而dispatchDraw不管怎样都会调用,具体为什么看源码^~^
第五步:就是重写onLayout摆放我们的HeaderView了,我们把HeaderView放在了控件的最顶端,(如果我们的ListView加了自己的真正意义上的HeaderView的话那么MyHeaderView摆放的位置的top需要加上自己的HeaderView的高度了)
第六步:也是我们本次研究的最重要的一步了,计算HeaderView显示的位置,判断是否需要更新和替换
在此之前,我们创建一个接口PinnedHeaderAdapter,然后自定义一个Adapter(PinnedAdapter),让PinnedAdapter去继承PinnedHeaderAdapter,让我们PinnedListView去回调,从而通知Adapter我们是否需要更新和替换Header
PinnedAdapte的代码如下(继承了PinnedHeaderAdapter跟OnScrollListener ):
重写ListView的setAdapter方法,因为我们的PinnedAdapte继承了PinnedHeaderAdapter,所以我们在这里获取的Adapter强转成PinnedHeaderAdapter获取实例
好了,万事具备了,来到我们最关键的configureHeaderView方法了
大功告成,测试一下我们的结果:
最后附上PinnedHeaderListView跟PinnedAdapter的全部代码:
本次研究的东西是PinnedHeaderListView,也就是头部悬浮,并且不断的更新,想必大家已经在很多地方看到过了,
![运行效果图如下]
实现的方式
(一)可以用一个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之PinnedHeaderExpandableListView- 仿ios的UITableView的header置顶效果
- Android自定义View之PinnerHeaderListView
- Android仿联系人列表分组悬浮列表实现,自定义PinnedHeaderListView实现
- android listview addHeaderView和addFooterView的注意事项
- android listview 多次addHeaderView()异常解决方法!
- Android开发_viewpager作为header加入到listview
- android 自定义TextView支持微博功能后在ListView占用了Item点击的解决办法
- android listView 自定义布局结合CheckedTextView实现多选
- android listview addHeaderView和addFooterView的注意事项
- android 自定义TextView支持微博功能后在ListView占用了Item点击的解决办法
- Android 联系人PinnedHeaderListView
- android listview addHeaderView和addFooterView的注意事项
- android中listview添加2个headerview显示效果的演示
- android listview addHeaderView和addFooterView的注意事项
- Android ListView自定义分割线 header 和footer设置没有页眉和页脚
- 实现自定义view(2):仿Android QQ多屏幕显示ListView的效果
- Android ListView分页时出现java.lang.ClassCastException: android.widget.HeaderViewListAdapter异常
- android ListView addHeaderView问题
- [android] 关于ListView的 addHeaderView(...) 方法
- (转)android listview addHeaderView和addFooterView的注意事项