├── .classpath ├── .project ├── AndroidManifest.xml ├── README.md ├── ic_launcher-web.png ├── libs └── android-support-v4.jar ├── proguard-project.txt ├── project.properties ├── res ├── anim │ ├── loading_anim.xml │ ├── reverse_anim.xml │ └── rotating.xml ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ ├── ic_launcher.png │ ├── loading.png │ ├── loading_01.png │ ├── loading_02.png │ ├── loading_03.png │ ├── loading_04.png │ ├── loading_05.png │ ├── loading_06.png │ ├── loading_07.png │ ├── loading_08.png │ ├── loading_09.png │ ├── loading_10.png │ ├── loading_11.png │ ├── loading_12.png │ ├── pull_icon_big.png │ ├── refresh_failed.png │ ├── refresh_succeed.png │ └── refreshing.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── layout │ ├── activity_listview.xml │ ├── list_item_layout.xml │ ├── load_more.xml │ └── refresh_head.xml ├── values-sw600dp │ └── dimens.xml ├── values-sw720dp-land │ └── dimens.xml └── values │ ├── color.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── screenshot.gif └── src └── com └── jingchen └── autoload ├── MainActivity.java ├── MyAdapter.java ├── MyListener.java ├── PullToRefreshLayout.java ├── Pullable.java └── PullableListView.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | AutoLoad 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AutoLoad 2 | ======== 3 | 4 | 滑动到底部自动加载的ListView,实现原理参见博客[http://blog.csdn.net/zhongkejingwang/article/details/38963177](http://blog.csdn.net/zhongkejingwang/article/details/38963177) 5 | ###效果图 6 | ![](https://github.com/jingchenUSTC/AutoLoad/blob/master/screenshot.gif) 7 | -------------------------------------------------------------------------------- /ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/ic_launcher-web.png -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/libs/android-support-v4.jar -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-8 15 | -------------------------------------------------------------------------------- /res/anim/loading_anim.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 41 | -------------------------------------------------------------------------------- /res/anim/reverse_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /res/anim/rotating.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_01.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_02.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_03.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_04.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_05.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_06.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_07.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_08.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_09.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_10.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_11.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/loading_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/loading_12.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/pull_icon_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/pull_icon_big.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/refresh_failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/refresh_failed.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/refresh_succeed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/refresh_succeed.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/refreshing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xhdpi/refreshing.png -------------------------------------------------------------------------------- /res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/layout/activity_listview.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /res/layout/list_item_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /res/layout/load_more.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 26 | 27 | -------------------------------------------------------------------------------- /res/layout/refresh_head.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 19 | 20 | 27 | 28 | 36 | 37 | 45 | 46 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 128dp 8 | 9 | 10 | -------------------------------------------------------------------------------- /res/values/color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #000000 5 | #eeeeee 6 | #6593cb 7 | 8 | -------------------------------------------------------------------------------- /res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 8 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 滑到底部自动加载控件 5 | 下拉刷新 6 | 释放立即刷新 7 | 正在刷新... 8 | 刷新成功 9 | 刷新失败 10 | 更多 11 | 正在加载... 12 | 13 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jingchenUSTC/AutoLoad/8b89a9bc6b318a28976035005ff7fa68db6d3a7a/screenshot.gif -------------------------------------------------------------------------------- /src/com/jingchen/autoload/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jingchen.autoload; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import android.app.Activity; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.os.Message; 10 | import android.view.View; 11 | import android.widget.AdapterView; 12 | import android.widget.AdapterView.OnItemClickListener; 13 | import android.widget.AdapterView.OnItemLongClickListener; 14 | import android.widget.Toast; 15 | 16 | import com.jingchen.autoload.PullableListView.OnLoadListener; 17 | 18 | /** 19 | * 更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38963177 20 | * @author chenjing 21 | * 22 | */ 23 | public class MainActivity extends Activity implements OnLoadListener 24 | { 25 | 26 | private PullableListView listView; 27 | private MyAdapter adapter; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) 31 | { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_listview); 34 | ((PullToRefreshLayout) findViewById(R.id.refresh_view)) 35 | .setOnRefreshListener(new MyListener()); 36 | listView = (PullableListView) findViewById(R.id.content_view); 37 | initListView(); 38 | listView.setOnLoadListener(this); 39 | } 40 | 41 | /** 42 | * ListView初始化方法 43 | */ 44 | private void initListView() 45 | { 46 | List items = new ArrayList(); 47 | for (int i = 0; i < 10; i++) 48 | { 49 | items.add("这里是item " + i); 50 | } 51 | adapter = new MyAdapter(this, items); 52 | listView.setAdapter(adapter); 53 | listView.setOnItemLongClickListener(new OnItemLongClickListener() 54 | { 55 | 56 | @Override 57 | public boolean onItemLongClick(AdapterView parent, View view, 58 | int position, long id) 59 | { 60 | Toast.makeText( 61 | MainActivity.this, 62 | "LongClick on " 63 | + parent.getAdapter().getItemId(position), 64 | Toast.LENGTH_SHORT).show(); 65 | return true; 66 | } 67 | }); 68 | listView.setOnItemClickListener(new OnItemClickListener() 69 | { 70 | 71 | @Override 72 | public void onItemClick(AdapterView parent, View view, 73 | int position, long id) 74 | { 75 | Toast.makeText(MainActivity.this, 76 | " Click on " + parent.getAdapter().getItemId(position), 77 | Toast.LENGTH_SHORT).show(); 78 | } 79 | }); 80 | } 81 | 82 | @Override 83 | public void onLoad(final PullableListView pullableListView) 84 | { 85 | new Handler() 86 | { 87 | @Override 88 | public void handleMessage(Message msg) 89 | { 90 | for (int i = 0; i < 5; i++) 91 | adapter.addItem("这里是自动加载进来的item"); 92 | // 千万别忘了告诉控件加载完毕了哦! 93 | pullableListView.finishLoading(); 94 | } 95 | }.sendEmptyMessageDelayed(0, 5000); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/com/jingchen/autoload/MyAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jingchen.autoload; 2 | 3 | import java.util.List; 4 | 5 | import android.content.Context; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.BaseAdapter; 10 | import android.widget.TextView; 11 | 12 | public class MyAdapter extends BaseAdapter 13 | { 14 | List items; 15 | Context context; 16 | 17 | public MyAdapter(Context context, List items) 18 | { 19 | this.context = context; 20 | this.items = items; 21 | } 22 | 23 | public void addItem(String text) 24 | { 25 | items.add(text); 26 | notifyDataSetChanged(); 27 | } 28 | 29 | @Override 30 | public int getCount() 31 | { 32 | return items.size(); 33 | } 34 | 35 | @Override 36 | public Object getItem(int position) 37 | { 38 | return items.get(position); 39 | } 40 | 41 | @Override 42 | public long getItemId(int position) 43 | { 44 | return position; 45 | } 46 | 47 | @Override 48 | public View getView(int position, View convertView, ViewGroup parent) 49 | { 50 | View view = LayoutInflater.from(context).inflate( 51 | R.layout.list_item_layout, null); 52 | TextView tv = (TextView) view.findViewById(R.id.tv); 53 | tv.setText(items.get(position)); 54 | return view; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/com/jingchen/autoload/MyListener.java: -------------------------------------------------------------------------------- 1 | package com.jingchen.autoload; 2 | 3 | import android.os.Handler; 4 | import android.os.Message; 5 | 6 | import com.jingchen.autoload.PullToRefreshLayout.OnRefreshListener; 7 | 8 | public class MyListener implements OnRefreshListener 9 | { 10 | 11 | @Override 12 | public void onRefresh(final PullToRefreshLayout pullToRefreshLayout) 13 | { 14 | // 下拉刷新操作 15 | new Handler() 16 | { 17 | @Override 18 | public void handleMessage(Message msg) 19 | { 20 | // 千万别忘了告诉控件刷新完毕了哦! 21 | pullToRefreshLayout.refreshFinish(PullToRefreshLayout.SUCCEED); 22 | } 23 | }.sendEmptyMessageDelayed(0, 5000); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/com/jingchen/autoload/PullToRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.jingchen.autoload; 2 | 3 | import java.util.Timer; 4 | import java.util.TimerTask; 5 | 6 | import android.content.Context; 7 | import android.os.Handler; 8 | import android.os.Message; 9 | import android.util.AttributeSet; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.animation.AnimationUtils; 14 | import android.view.animation.LinearInterpolator; 15 | import android.view.animation.RotateAnimation; 16 | import android.widget.RelativeLayout; 17 | import android.widget.TextView; 18 | 19 | /** 20 | * 更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38868463 21 | * 22 | * @author 陈靖 23 | */ 24 | public class PullToRefreshLayout extends RelativeLayout 25 | { 26 | public static final String TAG = "PullToRefreshLayout"; 27 | // 初始状态 28 | public static final int INIT = 0; 29 | // 释放刷新 30 | public static final int RELEASE_TO_REFRESH = 1; 31 | // 正在刷新 32 | public static final int REFRESHING = 2; 33 | // 操作完毕 34 | public static final int DONE = 3; 35 | // 当前状态 36 | private int state = INIT; 37 | // 刷新回调接口 38 | private OnRefreshListener mListener; 39 | // 刷新成功 40 | public static final int SUCCEED = 0; 41 | // 刷新失败 42 | public static final int FAIL = 1; 43 | // 按下Y坐标,上一个事件点Y坐标 44 | private float downY, lastY; 45 | 46 | // 下拉的距离。注意:pullDownY和pullUpY不可能同时不为0 47 | public float pullDownY = 0; 48 | 49 | // 释放刷新的距离 50 | private float refreshDist = 200; 51 | 52 | private MyTimer timer; 53 | // 回滚速度 54 | public float MOVE_SPEED = 8; 55 | // 第一次执行布局 56 | private boolean isLayout = false; 57 | // 在刷新过程中滑动操作 58 | private boolean isTouch = false; 59 | // 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化 60 | private float radio = 2; 61 | 62 | // 下拉箭头的转180°动画 63 | private RotateAnimation rotateAnimation; 64 | // 均匀旋转动画 65 | private RotateAnimation refreshingAnimation; 66 | 67 | // 下拉头 68 | private View refreshView; 69 | // 下拉的箭头 70 | private View pullView; 71 | // 正在刷新的图标 72 | private View refreshingView; 73 | // 刷新结果图标 74 | private View refreshStateImageView; 75 | // 刷新结果:成功或失败 76 | private TextView refreshStateTextView; 77 | 78 | // 实现了Pullable接口的View 79 | private View pullableView; 80 | // 过滤多点触碰 81 | private int mEvents; 82 | 83 | /** 84 | * 执行自动回滚的handler 85 | */ 86 | Handler updateHandler = new Handler() 87 | { 88 | 89 | @Override 90 | public void handleMessage(Message msg) 91 | { 92 | // 回弹速度随下拉距离moveDeltaY增大而增大 93 | MOVE_SPEED = (float) (8 + 5 * Math.tan(Math.PI / 2 94 | / getMeasuredHeight() * (pullDownY))); 95 | if (!isTouch) 96 | { 97 | // 正在刷新,且没有往上推的话则悬停,显示"正在刷新..." 98 | if (state == REFRESHING && pullDownY <= refreshDist) 99 | { 100 | pullDownY = refreshDist; 101 | timer.cancel(); 102 | } 103 | 104 | } 105 | if (pullDownY > 0) 106 | pullDownY -= MOVE_SPEED; 107 | if (pullDownY <= 0) 108 | { 109 | // 已完成回弹 110 | pullDownY = 0; 111 | pullView.clearAnimation(); 112 | // 隐藏下拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态 113 | if (state != REFRESHING) 114 | changeState(INIT); 115 | timer.cancel(); 116 | } 117 | // 刷新布局,会自动调用onLayout 118 | requestLayout(); 119 | } 120 | 121 | }; 122 | 123 | public void setOnRefreshListener(OnRefreshListener listener) 124 | { 125 | mListener = listener; 126 | } 127 | 128 | public PullToRefreshLayout(Context context) 129 | { 130 | super(context); 131 | initView(context); 132 | } 133 | 134 | public PullToRefreshLayout(Context context, AttributeSet attrs) 135 | { 136 | super(context, attrs); 137 | initView(context); 138 | } 139 | 140 | public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyle) 141 | { 142 | super(context, attrs, defStyle); 143 | initView(context); 144 | } 145 | 146 | private void initView(Context context) 147 | { 148 | timer = new MyTimer(updateHandler); 149 | rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation( 150 | context, R.anim.reverse_anim); 151 | refreshingAnimation = (RotateAnimation) AnimationUtils.loadAnimation( 152 | context, R.anim.rotating); 153 | // 添加匀速转动动画 154 | LinearInterpolator lir = new LinearInterpolator(); 155 | rotateAnimation.setInterpolator(lir); 156 | refreshingAnimation.setInterpolator(lir); 157 | } 158 | 159 | private void hide() 160 | { 161 | timer.schedule(5); 162 | } 163 | 164 | /** 165 | * 完成刷新操作,显示刷新结果。注意:刷新完成后一定要调用这个方法 166 | */ 167 | /** 168 | * @param refreshResult 169 | * PullToRefreshLayout.SUCCEED代表成功,PullToRefreshLayout.FAIL代表失败 170 | */ 171 | public void refreshFinish(int refreshResult) 172 | { 173 | refreshingView.clearAnimation(); 174 | refreshingView.setVisibility(View.GONE); 175 | switch (refreshResult) 176 | { 177 | case SUCCEED: 178 | // 刷新成功 179 | refreshStateImageView.setVisibility(View.VISIBLE); 180 | refreshStateTextView.setText(R.string.refresh_succeed); 181 | refreshStateImageView 182 | .setBackgroundResource(R.drawable.refresh_succeed); 183 | break; 184 | case FAIL: 185 | default: 186 | // 刷新失败 187 | refreshStateImageView.setVisibility(View.VISIBLE); 188 | refreshStateTextView.setText(R.string.refresh_fail); 189 | refreshStateImageView 190 | .setBackgroundResource(R.drawable.refresh_failed); 191 | break; 192 | } 193 | // 刷新结果停留1秒 194 | new Handler() 195 | { 196 | @Override 197 | public void handleMessage(Message msg) 198 | { 199 | changeState(DONE); 200 | hide(); 201 | } 202 | }.sendEmptyMessageDelayed(0, 1000); 203 | } 204 | 205 | private void changeState(int to) 206 | { 207 | state = to; 208 | switch (state) 209 | { 210 | case INIT: 211 | // 下拉布局初始状态 212 | refreshStateImageView.setVisibility(View.GONE); 213 | refreshStateTextView.setText(R.string.pull_to_refresh); 214 | pullView.clearAnimation(); 215 | pullView.setVisibility(View.VISIBLE); 216 | break; 217 | case RELEASE_TO_REFRESH: 218 | // 释放刷新状态 219 | refreshStateTextView.setText(R.string.release_to_refresh); 220 | pullView.startAnimation(rotateAnimation); 221 | break; 222 | case REFRESHING: 223 | // 正在刷新状态 224 | pullView.clearAnimation(); 225 | refreshingView.setVisibility(View.VISIBLE); 226 | pullView.setVisibility(View.INVISIBLE); 227 | refreshingView.startAnimation(refreshingAnimation); 228 | refreshStateTextView.setText(R.string.refreshing); 229 | break; 230 | case DONE: 231 | // 刷新完毕,啥都不做 232 | break; 233 | } 234 | } 235 | 236 | /* 237 | * (非 Javadoc)由父控件决定是否分发事件,防止事件冲突 238 | * 239 | * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent) 240 | */ 241 | @Override 242 | public boolean dispatchTouchEvent(MotionEvent ev) 243 | { 244 | switch (ev.getActionMasked()) 245 | { 246 | case MotionEvent.ACTION_DOWN: 247 | downY = ev.getY(); 248 | lastY = downY; 249 | timer.cancel(); 250 | mEvents = 0; 251 | break; 252 | case MotionEvent.ACTION_POINTER_DOWN: 253 | case MotionEvent.ACTION_POINTER_UP: 254 | // 过滤多点触碰 255 | mEvents = -1; 256 | break; 257 | case MotionEvent.ACTION_MOVE: 258 | if (mEvents == 0) 259 | { 260 | if (((Pullable) pullableView).canPullDown()) 261 | { 262 | // 可以下拉,正在加载时不能下拉 263 | // 对实际滑动距离做缩小,造成用力拉的感觉 264 | pullDownY = pullDownY + (ev.getY() - lastY) / radio; 265 | if (pullDownY < 0) 266 | { 267 | pullDownY = 0; 268 | } 269 | if (pullDownY > getMeasuredHeight()) 270 | pullDownY = getMeasuredHeight(); 271 | if (state == REFRESHING) 272 | { 273 | // 正在刷新的时候触摸移动 274 | isTouch = true; 275 | } 276 | } 277 | } else 278 | mEvents = 0; 279 | lastY = ev.getY(); 280 | // 根据下拉距离改变比例 281 | radio = (float) (2 + 2 * Math.tan(Math.PI / 2 / getMeasuredHeight() 282 | * (pullDownY))); 283 | requestLayout(); 284 | if (pullDownY <= refreshDist && state == RELEASE_TO_REFRESH) 285 | { 286 | // 如果下拉距离没达到刷新的距离且当前状态是释放刷新,改变状态为下拉刷新 287 | changeState(INIT); 288 | } 289 | if (pullDownY >= refreshDist && state == INIT) 290 | { 291 | // 如果下拉距离达到刷新的距离且当前状态是初始状态刷新,改变状态为释放刷新 292 | changeState(RELEASE_TO_REFRESH); 293 | } 294 | // 因为刷新和加载操作不能同时进行,所以pullDownY和pullUpY不会同时不为0,因此这里用(pullDownY + 295 | // Math.abs(pullUpY))就可以不对当前状态作区分了 296 | if ((pullDownY) > 8) 297 | { 298 | // 防止下拉过程中误触发长按事件和点击事件 299 | ev.setAction(MotionEvent.ACTION_CANCEL); 300 | } 301 | break; 302 | case MotionEvent.ACTION_UP: 303 | if (pullDownY > refreshDist) 304 | // 正在刷新时往下拉(正在加载时往上拉),释放后下拉头(上拉头)不隐藏 305 | isTouch = false; 306 | if (state == RELEASE_TO_REFRESH) 307 | { 308 | changeState(REFRESHING); 309 | // 刷新操作 310 | if (mListener != null) 311 | mListener.onRefresh(this); 312 | } 313 | hide(); 314 | default: 315 | break; 316 | } 317 | // 事件分发交给父类 318 | super.dispatchTouchEvent(ev); 319 | return true; 320 | } 321 | 322 | private void initView() 323 | { 324 | // 初始化下拉布局 325 | pullView = refreshView.findViewById(R.id.pull_icon); 326 | refreshStateTextView = (TextView) refreshView 327 | .findViewById(R.id.state_tv); 328 | refreshingView = refreshView.findViewById(R.id.refreshing_icon); 329 | refreshStateImageView = refreshView.findViewById(R.id.state_iv); 330 | } 331 | 332 | @Override 333 | protected void onLayout(boolean changed, int l, int t, int r, int b) 334 | { 335 | if (!isLayout) 336 | { 337 | // 这里是第一次进来的时候做一些初始化 338 | refreshView = getChildAt(0); 339 | pullableView = getChildAt(1); 340 | isLayout = true; 341 | initView(); 342 | refreshDist = ((ViewGroup) refreshView).getChildAt(0) 343 | .getMeasuredHeight(); 344 | } 345 | // 改变子控件的布局,这里直接用(pullDownY + pullUpY)作为偏移量,这样就可以不对当前状态作区分 346 | refreshView.layout(0, 347 | (int) (pullDownY) - refreshView.getMeasuredHeight(), 348 | refreshView.getMeasuredWidth(), (int) (pullDownY)); 349 | pullableView.layout(0, (int) (pullDownY), 350 | pullableView.getMeasuredWidth(), (int) (pullDownY) 351 | + pullableView.getMeasuredHeight()); 352 | } 353 | 354 | class MyTimer 355 | { 356 | private Handler handler; 357 | private Timer timer; 358 | private MyTask mTask; 359 | 360 | public MyTimer(Handler handler) 361 | { 362 | this.handler = handler; 363 | timer = new Timer(); 364 | } 365 | 366 | public void schedule(long period) 367 | { 368 | if (mTask != null) 369 | { 370 | mTask.cancel(); 371 | mTask = null; 372 | } 373 | mTask = new MyTask(handler); 374 | timer.schedule(mTask, 0, period); 375 | } 376 | 377 | public void cancel() 378 | { 379 | if (mTask != null) 380 | { 381 | mTask.cancel(); 382 | mTask = null; 383 | } 384 | } 385 | 386 | class MyTask extends TimerTask 387 | { 388 | private Handler handler; 389 | 390 | public MyTask(Handler handler) 391 | { 392 | this.handler = handler; 393 | } 394 | 395 | @Override 396 | public void run() 397 | { 398 | handler.obtainMessage().sendToTarget(); 399 | } 400 | 401 | } 402 | } 403 | 404 | /** 405 | * 刷新加载回调接口 406 | * 407 | * @author chenjing 408 | * 409 | */ 410 | public interface OnRefreshListener 411 | { 412 | /** 413 | * 刷新操作 414 | */ 415 | void onRefresh(PullToRefreshLayout pullToRefreshLayout); 416 | 417 | } 418 | 419 | } 420 | -------------------------------------------------------------------------------- /src/com/jingchen/autoload/Pullable.java: -------------------------------------------------------------------------------- 1 | package com.jingchen.autoload; 2 | 3 | public interface Pullable 4 | { 5 | /** 6 | * 判断是否可以下拉,如果不需要下拉功能可以直接return false 7 | * 8 | * @return true如果可以下拉否则返回false 9 | */ 10 | boolean canPullDown(); 11 | } 12 | -------------------------------------------------------------------------------- /src/com/jingchen/autoload/PullableListView.java: -------------------------------------------------------------------------------- 1 | package com.jingchen.autoload; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.AnimationDrawable; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | import android.widget.ImageView; 10 | import android.widget.ListView; 11 | import android.widget.TextView; 12 | 13 | /** 14 | * 如果不需要下拉刷新直接在canPullDown中返回false,这里的自动加载和下拉刷新没有冲突,通过增加在尾部的footerview实现自动加载, 15 | * 所以在使用中不要再动footerview了 16 | * 更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38963177 17 | * @author chenjing 18 | * 19 | */ 20 | public class PullableListView extends ListView implements Pullable 21 | { 22 | public static final int INIT = 0; 23 | public static final int LOADING = 1; 24 | private OnLoadListener mOnLoadListener; 25 | private View mLoadmoreView; 26 | private ImageView mLoadingView; 27 | private TextView mStateTextView; 28 | private int state = INIT; 29 | private boolean canLoad = true; 30 | private boolean autoLoad = true; 31 | private AnimationDrawable mLoadAnim; 32 | 33 | public PullableListView(Context context) 34 | { 35 | super(context); 36 | init(context); 37 | } 38 | 39 | public PullableListView(Context context, AttributeSet attrs) 40 | { 41 | super(context, attrs); 42 | init(context); 43 | } 44 | 45 | public PullableListView(Context context, AttributeSet attrs, int defStyle) 46 | { 47 | super(context, attrs, defStyle); 48 | init(context); 49 | } 50 | 51 | private void init(Context context) 52 | { 53 | mLoadmoreView = LayoutInflater.from(context).inflate(R.layout.load_more, 54 | null); 55 | mLoadingView = (ImageView) mLoadmoreView.findViewById(R.id.loading_icon); 56 | mLoadingView.setBackgroundResource(R.anim.loading_anim); 57 | mLoadAnim = (AnimationDrawable) mLoadingView.getBackground(); 58 | mStateTextView = (TextView) mLoadmoreView.findViewById(R.id.loadstate_tv); 59 | mLoadmoreView.setOnClickListener(new OnClickListener() 60 | { 61 | 62 | @Override 63 | public void onClick(View v) 64 | { 65 | //点击加载 66 | if(state != LOADING){ 67 | load(); 68 | } 69 | } 70 | }); 71 | addFooterView(mLoadmoreView, null, false); 72 | } 73 | 74 | /** 75 | * 是否开启自动加载 76 | * @param enable true启用,false禁用 77 | */ 78 | public void enableAutoLoad(boolean enable){ 79 | autoLoad = enable; 80 | } 81 | 82 | /** 83 | * 是否显示加载更多 84 | * @param v true显示,false不显示 85 | */ 86 | public void setLoadmoreVisible(boolean v){ 87 | if(v) 88 | { 89 | addFooterView(mLoadmoreView, null, false); 90 | } 91 | else { 92 | removeFooterView(mLoadmoreView); 93 | } 94 | } 95 | 96 | @Override 97 | public boolean onTouchEvent(MotionEvent ev) 98 | { 99 | switch (ev.getActionMasked()) 100 | { 101 | case MotionEvent.ACTION_DOWN: 102 | // 按下的时候禁止自动加载 103 | canLoad = false; 104 | break; 105 | case MotionEvent.ACTION_UP: 106 | // 松开手判断是否自动加载 107 | canLoad = true; 108 | checkLoad(); 109 | break; 110 | } 111 | return super.onTouchEvent(ev); 112 | } 113 | 114 | @Override 115 | protected void onScrollChanged(int l, int t, int oldl, int oldt) 116 | { 117 | super.onScrollChanged(l, t, oldl, oldt); 118 | // 在滚动中判断是否满足自动加载条件 119 | checkLoad(); 120 | } 121 | 122 | /** 123 | * 判断是否满足自动加载条件 124 | */ 125 | private void checkLoad() 126 | { 127 | if (reachBottom() && mOnLoadListener != null && state != LOADING 128 | && canLoad && autoLoad) 129 | load(); 130 | } 131 | 132 | private void load(){ 133 | changeState(LOADING); 134 | mOnLoadListener.onLoad(this); 135 | } 136 | 137 | private void changeState(int state) 138 | { 139 | this.state = state; 140 | switch (state) 141 | { 142 | case INIT: 143 | mLoadAnim.stop(); 144 | mLoadingView.setVisibility(View.INVISIBLE); 145 | mStateTextView.setText(R.string.more); 146 | break; 147 | 148 | case LOADING: 149 | mLoadingView.setVisibility(View.VISIBLE); 150 | mLoadAnim.start(); 151 | mStateTextView.setText(R.string.loading); 152 | break; 153 | } 154 | } 155 | 156 | /** 157 | * 完成加载 158 | */ 159 | public void finishLoading() 160 | { 161 | changeState(INIT); 162 | } 163 | 164 | @Override 165 | public boolean canPullDown() 166 | { 167 | if (getCount() == 0) 168 | { 169 | // 没有item的时候也可以下拉刷新 170 | return true; 171 | } else if (getFirstVisiblePosition() == 0 172 | && getChildAt(0).getTop() >= 0) 173 | { 174 | // 滑到ListView的顶部了 175 | return true; 176 | } else 177 | return false; 178 | } 179 | 180 | public void setOnLoadListener(OnLoadListener listener) 181 | { 182 | this.mOnLoadListener = listener; 183 | } 184 | 185 | /** 186 | * @return footerview可见时返回true,否则返回false 187 | */ 188 | private boolean reachBottom() 189 | { 190 | if (getCount() == 0) 191 | { 192 | return true; 193 | } else if (getLastVisiblePosition() == (getCount() - 1)) 194 | { 195 | // 滑到底部,且頂部不是第0个,也就是说item数超过一屏后才能自动加载,否则只能点击加载 196 | if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null 197 | && getChildAt( 198 | getLastVisiblePosition() 199 | - getFirstVisiblePosition()).getTop() < getMeasuredHeight() && !canPullDown()) 200 | return true; 201 | } 202 | return false; 203 | } 204 | 205 | public interface OnLoadListener 206 | { 207 | void onLoad(PullableListView pullableListView); 208 | } 209 | } 210 | --------------------------------------------------------------------------------