PS:做android已经有一年的时间了,在外包干了一年,所以也做了将近10多个app了,各种类型的都有,虽然做了很多app,也实现了很多功能,只能说你现在给我一个需求,我能自己查查资料倒腾倒腾我能把它做出来,所以我还是停留在会用的阶段,还没怎么深入的研究一个东西,还停留在初级程序员的道路,准备向中级程序员进发了,听了许多大牛们的成长之路,就是要不断的总结,光看没用,哪怕是照着别人的代码敲一遍,对于你来说也会有很多收获的,于是我开始写博客了,见证下自己的成长过程!大牛勿喷!^~^
本次研究的东西是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);
}
}
}