版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tiger_gy/article/details/88530938
自定义listView 实现 下拉刷新+下拉加载的功能
图上是最终的实现效果。
1、首先自定头部的下拉刷新布局 和底部加载更多的布局。
2、通过使用setPadding的设置默认让头部布局和底部布局隐藏。
3、listView下拉的时候通过修改padding让头布局显示出来。listView 上拉的时候也是通过修改padding 显示出加载更多。
4、触摸动态修改头布局根据paddingTop 来判断头部的显示
-paddingTop=0 完全显示。
-paddingTop<0 不完全显示。 松开后自动隐藏
-paddingTop>0 完全显示+顶部空白
5、松手后根据当前的paddingTop决定是否执行刷新。
-paddingTop<0 不完全显示 恢复
-paddingTop>0 完全显示,执行正在刷新。
下面直接上代码
mainActivity.java
package com.hz.refreshlist;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.hz.refreshlist.view.RefreshListView;
import java.util.ArrayList;
public class MainActivity extends Activity {
private RefreshListView lvListView;
private ArrayList<String> listDatas;
private MyAdapter myAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*去掉顶部标题 设置数据*/
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
lvListView = (RefreshListView) findViewById(R.id.lv_listView);
lvListView.setRefreshListener(new RefreshListView.OnRefreshListener() {
@Override
public void onRefresh() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
listDatas.add(0,"我是下拉刷新的数据"+Math.random()*10+"");
runOnUiThread(new Runnable() {
@Override
public void run() {
myAdapter.notifyDataSetChanged();
lvListView.onRefreshComplete();
}
});
}
}).start();
}
@Override
public void onLoadMore() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
listDatas.add("我是加载更多的数据"+Math.random()*10+"");
listDatas.add("我是加载更多的数据"+Math.random()*10+"");
listDatas.add("我是加载更多的数据"+Math.random()*10+"");
runOnUiThread(new Runnable() {
@Override
public void run() {
myAdapter.notifyDataSetChanged();
lvListView.onRefreshComplete();
}
});
}
}).start();
}
});
/*产生测试的数据*/
listDatas = new ArrayList<String>();
for (int i = 0; i < 30; i++) {
listDatas.add("这是一条ListView数据: " + i);
}
/*数据适配器*/
myAdapter = new MyAdapter();
lvListView.setAdapter(myAdapter);
}
private class MyAdapter extends BaseAdapter {
/**
* How many items are in the data set represented by this Adapter.
*
* @return Count of items.
*/
@Override
public int getCount() {
return listDatas.size();
}
/**
* Get the data item associated with the specified position in the data set.
*
* @param position Position of the item whose data we want within the adapter's
* data set.
* @return The data at the specified position.
*/
@Override
public Object getItem(int position) {
return listDatas.get(position);
}
/**
* Get the row id associated with the specified position in the list.
*
* @param position The position of the item within the adapter's data set whose row id we want.
* @return The id of the item at the specified position.
*/
@Override
public long getItemId(int position) {
return position;
}
/**
* Get a View that displays the data at the specified position in the data set. You can either
* create a View manually or inflate it from an XML layout file. When the View is inflated, the
* parent View (GridView, ListView...) will apply default layout parameters unless you use
* {@link LayoutInflater#inflate(int, ViewGroup, boolean)}
* to specify a root view and to prevent attachment to the root.
*
* @param position The position of the item within the adapter's data set of the item whose view
* we want.
* @param convertView The old view to reuse, if possible. Note: You should check that this view
* is non-null and of an appropriate type before using. If it is not possible to convert
* this view to display the correct data, this method can create a new view.
* Heterogeneous lists can specify their number of view types, so that this View is
* always of the right type (see {@link #getViewTypeCount()} and
* {@link #getItemViewType(int)}).
* @param parent The parent that this view will eventually be attached to
* @return A View corresponding to the data at the specified position.
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = new TextView(parent.getContext());
textView.setTextSize(18f);
textView.setTextColor(Color.BLUE);
textView.setText(listDatas.get(position));
return textView;
}
}
}
activity_mian.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.hz.refreshlist.MainActivity">
<com.hz.refreshlist.view.RefreshListView
android:id="@+id/lv_listView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.hz.refreshlist.view.RefreshListView>
</LinearLayout>
RefreshListView.java
package com.hz.refreshlist.view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.hz.refreshlist.R;
import java.text.SimpleDateFormat;
/**
* 包含下拉刷新功能的listView
* Created by Administrator on 2019/3/12.
*/
public class RefreshListView extends ListView implements AbsListView.OnScrollListener {
private View mHeaderView;
private int mHeaderViewHeight;
private int mFooterViewHeight;
private float dowY;//按下的Y轴的距离
private float moveY;//移动的Y轴的距离
public static final int PULL_TO_REFRESH = 0;// 下拉刷新
public static final int RELEASE_REFRESH = 1;// 释放刷新
public static final int REFRESHING = 2; // 刷新中
private int currentState = PULL_TO_REFRESH; // 当前刷新模式
private RotateAnimation rotateUpAnim; // 箭头向上动画
private RotateAnimation rotateDownAnim; // 箭头向下动画
private ImageView ivArrow;//箭头图片
private ProgressBar pbProgress;//滚动条
private TextView tvTitle;//头部局标题
private TextView tvDescLastRefresh;//时间刷新内容
private OnRefreshListener mListener; // 刷新监听
private View mFooterView;
public static int SCROLL_STATE_IDLE = 0; // 空闲
public static int SCROLL_STATE_TOUCH_SCROLL = 1; // 触摸滑动
public static int SCROLL_STATE_FLING = 2; // 滑翔
private boolean isLoadingMore; // 是否正在加载更多
/**
*代码里面
* @param context
*/
public RefreshListView(Context context) {
super(context);
init();
}
/**
* 包含属性
* @param context
* @param attrs
*/
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/**
*包含样式
* @param context
* @param attrs
* @param defStyleAttr
*/
public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 初始化头布局, 脚布局
* 滚动监听
*/
private void init() {
initHeaderView();
initAnimation();
initFooterView();
setOnScrollListener(this);
}
/**
* 初始化头布局
*/
private void initHeaderView() {
mHeaderView = View.inflate(getContext(), R.layout.layout_header_list,null);
ivArrow = (ImageView) mHeaderView.findViewById(R.id.iv_arrow);
pbProgress = (ProgressBar) mHeaderView.findViewById(R.id.pb_progress);
tvTitle = (TextView)mHeaderView. findViewById(R.id.tv_title);
tvDescLastRefresh = (TextView) mHeaderView.findViewById(R.id.tv_desc_last_refresh);
/*提前手动测量宽高*/
mHeaderView.measure(0, 0);
/*获取自身高度*/
mHeaderViewHeight = mHeaderView.getMeasuredHeight();
/*设置内边距,可以隐藏布局 -自身高度*/
mHeaderView.setPadding(0,-mHeaderViewHeight,0,0);
addHeaderView(mHeaderView);
}
/**
* 初始化脚布局
*/
private void initFooterView() {
mFooterView = View.inflate(getContext(), R.layout.layout_footer_list,null);
/*提前手动测量宽高*/
mFooterView.measure(0, 0);
/*获取自身高度*/
mFooterViewHeight = mFooterView.getMeasuredHeight();
/*设置内边距,可以隐藏布局 -自身高度*/
mFooterView.setPadding(0,-mFooterViewHeight,0,0);
addFooterView(mFooterView);
}
/**
* 初始化头布局的动画
*/
private void initAnimation() {
// 向上转, 围绕着自己的中心, 逆时针旋转0 -> -180.
rotateUpAnim=new RotateAnimation(0f,-180f,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f);
rotateUpAnim.setDuration(300);
// 动画停留在结束位置
rotateUpAnim.setFillAfter(true);
// 向下转, 围绕着自己的中心, 逆时针旋转 -180 -> -360
rotateDownAnim=new RotateAnimation(-180f,-360f,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f);
rotateDownAnim.setDuration(300);
rotateDownAnim.setFillAfter(true);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
/*判断滑动距离,给Header设置paddingTop*/
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
dowY =ev.getY();
break;
case MotionEvent.ACTION_MOVE:
moveY =ev.getY();
// 如果是正在刷新中, 就执行父类的处理
if(currentState == REFRESHING){
return super.onTouchEvent(ev);
}
// 移动的偏移量
float offset=moveY-dowY;
// 只有 偏移量>0, 并且当前第一个可见条目索引是0, 才放大头部
if (offset>0&&getFirstVisiblePosition()==0){
//int paddingTop = -自身高度 + 偏移量
int paddingTop= (int) (-mHeaderViewHeight+offset);
mHeaderView.setPadding(0,paddingTop,0,0);
if (paddingTop>=0&¤tState!=RELEASE_REFRESH){// 头布局完全显示
// 切换成释放刷新模式
currentState=RELEASE_REFRESH;
Log.d("RefreshListView", "释放刷新");
updateHeader();
}else if(paddingTop < 0 &¤tState!=PULL_TO_REFRESH){// 头布局不完全显示
// 切换下拉刷新模式
Log.d("RefreshListView", "下拉刷新");
currentState=PULL_TO_REFRESH;
updateHeader();
}
return true; // 当前事件被我们处理并消费
}
break;
case MotionEvent.ACTION_UP:
// 根据刚刚设置状态
if (currentState==PULL_TO_REFRESH){
//- paddingTop < 0 不完全显示, 恢复
mHeaderView.setPadding(0,-mHeaderViewHeight,0,0);
}else {
//- paddingTop >= 0 完全显示, 执行正在刷新...
mHeaderView.setPadding(0,0,0,0);
currentState = REFRESHING;
updateHeader();
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 根据状态更新头布局内容
*/
private void updateHeader() {
switch (currentState){
case PULL_TO_REFRESH://切换成下拉刷新
//切换动画,改标题
ivArrow.startAnimation(rotateDownAnim);
tvTitle.setText("下拉刷新");
break;
case RELEASE_REFRESH://切换成释放刷新
//切换动画,改标题
ivArrow.startAnimation(rotateUpAnim);
tvTitle.setText("释放刷新");
break;
case REFRESHING://切换成正在刷新
//切换动画改标题
ivArrow.clearAnimation();
ivArrow.setVisibility(INVISIBLE);
pbProgress.setVisibility(VISIBLE);
tvTitle.setText("正在刷新中");
if (mListener!=null){
mListener.onRefresh();
}
break;
}
}
/**
* 刷新结束恢复界面效果
*/
public void onRefreshComplete(){
/*是否加载更多*/
if (isLoadingMore){
// 加载更多
mFooterView.setPadding(0,-mFooterViewHeight,0,0);
isLoadingMore = false;
}else {
// 下拉刷新
currentState=PULL_TO_REFRESH;
tvTitle.setText("下拉刷新");
mHeaderView.setPadding(0,-mHeaderViewHeight,0,0);
ivArrow.setVisibility(VISIBLE);
pbProgress.setVisibility(INVISIBLE);
String time=getTime();
tvDescLastRefresh.setText("最后刷新时间:"+time);
}
}
private String getTime() {
long currentTimeMillis = System.currentTimeMillis();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(currentTimeMillis);
}
public void setRefreshListener(OnRefreshListener mListener){
this.mListener=mListener;
}
/**
* * public static int SCROLL_STATE_IDLE = 0; // 空闲
public static int SCROLL_STATE_TOUCH_SCROLL = 1; // 触摸滑动
public static int SCROLL_STATE_FLING = 2; // 滑翔
* @param view The view whose scroll state is being reported
* @param scrollState The current scroll state. One of
* {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 状态更新的时候
if(isLoadingMore){
return; // 已经在加载更多.返回
}
// 最新状态是空闲状态, 并且当前界面显示了所有数据的最后一条. 加载更多
if (scrollState==SCROLL_STATE_IDLE &&getLastVisiblePosition()>=(getCount() - 1)){
isLoadingMore=true;
Log.d("RefreshListView", "显示加载更多");
mFooterView.setPadding(0,0,0,0);
setSelection(getCount());
if (mListener!=null){
mListener.onLoadMore();
}
}
}
/**
* Callback method to be invoked when the list or grid has been scrolled. This will be
* called after the scroll has completed
* @param view The view whose scroll state is being reported
* @param firstVisibleItem the index of the first visible cell (ignore if
* visibleItemCount == 0)
* @param visibleItemCount the number of visible cells
* @param totalItemCount the number of items in the list adaptor
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
public interface OnRefreshListener{
void onRefresh(); // 下拉刷新
void onLoadMore();// 加载更多
}
}
layout_footer_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<ProgressBar
android:layout_margin="5dp"
android:layout_width="50dp"
android:layout_height="50dp"
android:indeterminateDrawable="@drawable/shape_progress" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加载更多..."
android:textColor="#F00"
android:layout_marginLeft="15dp"
android:textSize="18sp" />
</LinearLayout>
layout_header_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<FrameLayout
android:layout_margin="5dp"
android:layout_width="50dp"
android:layout_height="50dp" >
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/common_listview_headview_red_arrow" />
<ProgressBar
android:id="@+id/pb_progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:indeterminateDrawable="@drawable/shape_progress"
android:visibility="invisible"/>
</FrameLayout>
<LinearLayout
android:layout_margin="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="下拉刷新"
android:textColor="#F00"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_desc_last_refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:singleLine="true"
android:text="最后刷新时间: 2015-10-11 09:20:35"
android:textColor="#666"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
shape_progress.xml
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="-360" >
<!-- android:innerRadius="20dp"
android:thickness="5dp"-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadiusRatio="2.5"
android:shape="ring"
android:thicknessRatio="10"
android:useLevel="false" >
<gradient
android:centerColor="#44FF0000"
android:endColor="#00000000"
android:startColor="#ff0000"
android:type="sweep" />
</shape>
</rotate>
最后上传这个下拉的图片,大功告成了。