您的位置:首页 > 移动开发 > Android开发

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)如下:

<?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);
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: