现在很多应用都增加了一个可以下拉刷新的控件适应移动端的触摸新交互方式,这边教程就来探讨一下如何实现这个功能。
微博的实际效果图。
首先我们很直观的发现,下拉刷新列表中的列表元素实际上是大体相同的。所以可以直接用listview来作为容器装载。
之后的问题在于,我们该如何实现下拉刷新的功能。
android的特点让我们有多种方式实现一个ui功能。
下面提供两种思路。
1、通过listview自带的headerview来实现。
2、通过一个view和listview集合实现。
1、通过inflate方法生成headerview,然后通过addheaderview方法给listview增加
通过ontouchevent判断当前header相对于父控件的位置来决定是否更新。
此外 还需要定义一个借口pulldownlistener 来给外部实现更新的具体操作。
下面是代码:
package com.example.test_weibo;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.text.Selection;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class TestListView extends ListView implements OnScrollListener{
/**
* 头部的控件内容
* 可根据header布局文件适当扩展
*/
//头部的textview
private TextView refresh_TextView;
//头部的高度
private int mHeader_Heght;
//头部的view
private View header_View;
/*
* 工具成员变量
*
*/
//context 当前使用这个自定义的context
private Context context;
//inflate出实例的inflater
private LayoutInflater inflater;
/**
* 一些状态的常量值
* 另外可以 增加一些控件的属性值。如padding。
*/
private final int WHILE_REFRESH = 3;
private final int REFRESH_FINISHED = 4;
private final int PREPARE_REFRESH = 5;
private final int WHILE_SCROLLING = 1;
private final int SCROLLING_IDLE = 2;
private final int GO_TO_REFRESH = 6;
private final int FINISH_REFRESH = 7;
/*
* 当前滚动的状态。
*/
private int mNowScroolState = SCROLL_STATE_IDLE;
/**
* 当前的更新状态
*/
private int mNowRefreshState = PREPARE_REFRESH;
/**
* 当前的adapter
*/
private ListAdapter adapter;
//当前pulldownlistener
private pullDownListener mPullDownListener;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
switch (msg.what) {
case GO_TO_REFRESH://更新操作
setSelection(0);
mNowRefreshState = WHILE_REFRESH;
refresh_TextView.setText("更新中");
//mPullDownListener.onRefresh();
/**
* 做一个模拟的读取数据的过程实际上应该调pulldownlistener的onRefresh函数
*/
new Thread(){
public void run(){
try {
Thread.sleep(2000);
handler.sendEmptyMessage(FINISH_REFRESH);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
break;
case FINISH_REFRESH://结束更新时的ui操作。
mNowRefreshState = PREPARE_REFRESH;
reset_Header();
//setSelection(1);
break;
default:
break;
}
}
};
/**
* 三个构造函数
* @param context
* @param attrs
* @param defStyle
*/
public TestListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
init(context);
// TODO Auto-generated constructor stub
}
public TestListView(Context context ,AttributeSet attrs) {
// TODO Auto-generated constructor stub
super(context, attrs );
init(context);
}
public TestListView(Context context){
super( context );
}
/**
* 初始化头部控件
* @param context
*/
public void init(Context context){
this.context = context;
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
header_View = inflater.inflate(R.layout.header, this,false);
refresh_TextView = (TextView)header_View.findViewById(R.id.header);
addHeaderView(header_View);
super.setOnScrollListener(this);
measureView(header_View);
mHeader_Heght = header_View.getMeasuredHeight();
System.out.println("fucking_pad"+header_View.getPaddingTop());
System.out.println("height"+header_View.getMeasuredHeight());
this.setVerticalScrollBarEnabled(false);//设置垂直的滚动条不出现
}
@Override
public boolean onTouchEvent(MotionEvent event){
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://当手指按下的时候把滚动状态设置为滚动中
System.out.println("down");
mNowScroolState = WHILE_SCROLLING;
//Toast.makeText(context, "down", Toast.LENGTH_LONG).show();
break;
case MotionEvent.ACTION_MOVE://在移动的时候判断header的位置
if(header_View.getBottom() > mHeader_Heght / 2 ){
System.out.println("scroll_the_y"+event.getY());
ready_For_Refresh();
}
if(header_View.getBottom() <= mHeader_Heght / 2 ){
refresh_TextView.setText("下拉可更新");
}
System.out.println("header_height"+header_View.getHeight());
System.out.println("header_top"+header_View.getTop());
System.out.println("header_bottom"+header_View.getBottom());
//Toast.makeText(context, "move", Toast.LENGTH_SHORT).show();
setHeaderWhileFetching(event);
break;
case MotionEvent.ACTION_UP://手指抬起时将滚动状态设置为闲置。
//判断是否从refresh
//reset_Header();
if(header_View.getBottom() > mHeader_Heght / 2){
handler.sendEmptyMessage(GO_TO_REFRESH);
}else{
System.out.println("scroll_to_y"+mHeader_Heght);
reset_Header();
}
mNowScroolState = SCROLL_STATE_IDLE;
//Toast.makeText(context, "up", Toast.LENGTH_LONG).show();
break;
default://在其他情况将滚动状态为闲置状态。
//判断是否refresh
if(header_View.getBottom() < mHeader_Heght / 2){
handler.sendEmptyMessage(GO_TO_REFRESH);
//mNowRefreshState = WHILE_REFRESH;
}else{
System.out.println("scroll_to_y"+mHeader_Heght);
reset_Header();
}
mNowScroolState = SCROLL_STATE_IDLE;
break;
}
//回调原来的touchevent事件实现滚动
return super.onTouchEvent(event);
}
/**
* 重置头部view
*/
public void reset_Header(){
mNowRefreshState = PREPARE_REFRESH;
refresh_TextView.setText("下拉更新");
setSelection(1);
}
/**
* 调整textview的文字
*/
public void ready_For_Refresh(){
refresh_TextView.setText("松开可更新");
}
/**
* 没用的。。
* @param event
*/
public void setHeaderWhileFetching(MotionEvent event){
int count = event.getHistorySize();
float old_Y;
for(int i = 0 ; i < count;++i){
old_Y = event.getHistoricalY(i);
System.out.println("old_Y"+old_Y);
}
float new_Y = event.getY();
System.out.println( "new_Y " + new_Y);
}
/**
* 调用view的measure函数,计算出child的大小
* @param child
*/
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0,0 + 0, p.width);
int lpHeight = p.height;
System.out.println("fucking"+lpHeight);
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
System.out.println("first"+firstVisibleItem + visibleItemCount);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
/**
* 设置适配器,并把头部view隐藏
*/
@Override
public void setAdapter(ListAdapter adapter){
super.setAdapter(adapter);
setSelection(1);
}
/**
* this is the interface of listener to implement.
* you can change the function inside to adapt to your needs.
*
* @author wang
*
*/
public interface pullDownListener{
public void onRefresh();
public void onMore();
}
/**
* 设置下拉菜单
* @param p
*/
public void setOnPullDownListener(pullDownListener p){
this.mPullDownListener = p;
}
}
2、通过继承一个linearlayout来实现下拉列表的功能。
这个难度稍微大一点,但是从实际上来说也更贴近设计。