android实现仿QQ界面刷新
转载请注明出处:http://blog.csdn.net/wangpengfei_p/article/details/51420422
昨天想要实现一个下拉刷新的效果,本来想应该比较简单,因为之前在慕课网看见过类似的实现,记得是在listView里面添加footView或是添加headView,监听手指的点击滑动事件来控制view的显示或是隐藏,但是自己按照上面的代码来实现之后发现。这样做有一点不好的地方:
它判断是否刷新的依据是判断listView是不是滑动到了最后一个item。只要一旦滑动到最后一列就会自动刷新数据,我们并不能控制对数据刷新的取消。
于是我想到的QQ主界面的刷新功能:先看看QQ界面的刷新功能:
于是我就想实现这样的一个功能,但在网上搜索了好久大多的都是通过上述方式来实现的。还有一种方式是通过自定义ViewGroup来实现的(原谅我是新手,对于自定义view虽然接触了一些,仍然用不起来T_T)
今天回忆起郭霖大神的那篇博客Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现想了一下我是不是也可以通过动态设置view的margin来对上面的刷新条进行设置呢?
于是今天下午不断测试,不断调试,最终觉得效果有点类似QQ那样的效果了,就迫不及待的要写一篇博客了(≧▽≦)
毕竟第一篇博客,又是新手有什么不对的地方还望大家指正
先来看看最终的效果:
基本上和QQ的刷新很相似了,只是界面好篓的感觉。。。
所以来看看是怎么来实现的吧
对了里面有一些代码是借鉴自郭神的博文Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现有兴趣的可以看看这篇博文
先来创建一个项目,名为ListViewTest
接着在activity_main布局文件里面代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/load_layout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#FFFFFF"
android:gravity="center"
android:orientation="horizontal" >
<ImageView
android:id="@+id/main_activity_top_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@drawable/esc"
android:src="@drawable/esc" />
<TextView
android:id="@+id/main_activity_top_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新"
android:textSize="12sp" />
</LinearLayout>
<ListView
android:id="@+id/main_activity_lv"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</ListView>
</LinearLayout>
这个布局文件里面主要有两部分,一个是上面的LinearLayout,用于表示刷新界面的布局,里面有一个ImageView和一个TextView用于表示刷新布局的前面的图标和后面的提示文字。这里我们让它们居中显示,因为在后面我们要对这三个控件都要进行操作,所以都要给它们加上id。
还有里面的图片是我从qq的安装包里面随便找的。
另一部分是界面的主体内容部分,我们用一个listView来进行表示,因为listView更有代表性,尤其是在存在有手指滑动的情况下。我们也给它加上id。
好了布局文件我们写好了,之后就是MainActivity里面的逻辑代码了
之后是MainActivity的内容
package com.example.listviewtest;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements OnTouchListener {
// 测试的数据源
private String[] strs = new String[30];
private ListView listView;
private ArrayAdapter<String> adapter;
// 顶部刷新视图的view
private View top;
// 顶部刷新视图中的文字提示
private TextView topTV;
// 顶部刷新试图的图片提示
private ImageView topIv;
// 顶部刷新视图的参数,通过此参数来更改topMargin的值
private LayoutParams topParams;
/**
* 顶部刷新视图的高度,通过topmargin和这个值进行比较来判断刷新视图的状态
*/
private int topHeight = 80;
/**
* 当listview里面的第一个item可见的时候,初始化此值。即当刷新视图出现的时候手指所在的Y轴的位置
*/
private int currentY;
// 这三个值分别是手指按下,移动,和抬起的时候手指的Y轴位置
private int moveY;
private int downY;
private int upY;
/**
* 这个值用于判断
*/
private boolean isRead = false;
/**
* 这个值用于判断当前是不是正在加载数据,如果正在加载,则不对刷新视图重新进行操作,如果没有更新,则对刷新视图进行操作
*/
private boolean isLoading = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
initDatas();
initView();
initValues();
}
/**
* 这里使用handler来进行数据的刷新,因为过郭霖说过,在android3.0之后asyncTask变成了单队列
* 如果两个操作想并发进行,就应该直接用Thread
*/
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
new ScrollTask().execute(-10);
Toast.makeText(getApplicationContext(), "数据刷新完成", Toast.LENGTH_SHORT).show();
};
};
/**
* 初始化各值
*/
private void initValues() {
topParams = (LayoutParams) top.getLayoutParams();
topParams.height = topHeight;
topParams.topMargin = -topHeight;
}
/**
* 初始化界面控件,以及给控件添加监听事件
*/
private void initView() {
listView = (ListView) findViewById(R.id.main_activity_lv);
top = findViewById(R.id.load_layout);
topTV = (TextView) findViewById(R.id.main_activity_top_tv);
topIv = (ImageView) findViewById(R.id.main_activity_top_iv);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, strs);
listView.setAdapter(adapter);
listView.setOnTouchListener(this);
}
/**
* 初始化数据源
*/
private void initDatas() {
for (int i = 0; i < strs.length; i++) {
strs[i] = String.valueOf(i);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int actionValue = event.getAction();
int margin = 0;
switch (actionValue) {
case MotionEvent.ACTION_DOWN:
downY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//如果没有加载数据则对刷新界面进行操作
if (!isLoading) {
moveY = (int) event.getRawY();
if (listView.getFirstVisiblePosition() == 0 && !isRead) {
isRead = true;
currentY = moveY;
}
if (isRead && currentY >= downY) {
margin = -(topHeight - (moveY - currentY));
changeTop(margin);
}
}
break;
case MotionEvent.ACTION_UP:
isRead = false;
if (!isLoading) {
upY = (int) event.getRawY();
// 计算手指离开的时候刷新视图的margin值
margin = upY - currentY - topHeight;
if (margin < -topHeight) {
margin = -topHeight;
}
if (margin > 0) {
margin = 0;
}
// 如果手指离开的时候margin小于top布局的高度的话则就不进行刷新,否则就进行刷新
if (Math.abs(margin) == 0) {
topIv.setImageResource(R.drawable.qapp_center_ico_loading);
topTV.setText("正在加载...");
Load();
isLoading = true;
} else if (margin > -topHeight) {
new ScrollTask().execute(-10);
isLoading = true;
}
break;
}
}
return false;
}
// 加载数据
private void Load() {
new Thread() {
@Override
public void run() {
try {
// 模拟异步加载数据所消耗的时间
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}.start();
}
/**
* 根据margin的值来改变刷新视图的状态
*
* @param margin
*/
private void changeTop(int margin) {
//如果margin的值大于刷新界面的宽度的话,就把它设置为刷新界面的宽度,因为不能让整个刷新界面离开顶部
if (margin < (-topHeight)) {
margin = (-topHeight);
}
//同理,为保证整个刷新界面不离开顶部,margin也不能大于零
if (margin > 0) {
margin = 0;
topTV.setText("松开进行刷新...");
topIv.setRotation(180F);
}
topParams.topMargin = margin;
top.setLayoutParams(topParams);
}
/**
* 数据加载完成的时候调用,将刷新视图初始化 并将是否正在加载数据的判断属性设置为false
*/
public void loadComplete() {
topIv.setImageResource(R.drawable.esc);
topIv.setRotation(0F);
topTV.setText("下拉刷新...");
isLoading = false;
}
class ScrollTask extends AsyncTask<Integer, Integer, Integer> {
@Override
protected Integer doInBackground(Integer... params) {
int topMargin = topParams.topMargin;
while (true) {
topMargin = topMargin + params[0];
if (topMargin > 0) {
topMargin = 0;
break;
}
if (topMargin < -topHeight) {
topMargin = -topHeight;
break;
}
publishProgress(topMargin);
// 为了要有滚动效果产生,每次循环使线程睡眠20毫秒,这样肉眼才能够看到滚动画面
sleep(20);
}
return topMargin;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
topParams.topMargin = values[0];
top.setLayoutParams(topParams);
}
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
topParams.topMargin = result;
top.setLayoutParams(topParams);
loadComplete();
}
/**
* 使当前线程睡眠指定的毫秒数
*
* @param i
*/
private void sleep(int i) {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
里面的代码有点多,我先来说一下主体思路:
- 将刷新界面的marginTop值设置为它的宽度的负值就能让它隐藏
- 先初始化各个值,包括各个控件,以及给各个控件添加监听事件,以及给listView初始化数据,这些不用对说。
- 之后的一些值有必要说明,currentY表示当滑动到顶部的时候手指所在的位置滑动过程中的值减去这个值来计算margin的值。里面的topHeight用来表示顶部的刷新界面的高度,之后的很多判断都要和这个值进行比较,isLoading这个值是用来判断现在是不是正在加载数据,如果正在加载数据,则不应该再对刷新界面进行操作。isRead这个值比较尴尬,因为这是我想到的一个解决方法,因为currentY是在监听事件的move状态中被确定的,只能初始化一次,而Touch里面的move状态只要手指一动就会调用。必须有一个值来标识currentY已经被初始化了。topParams只要用它来设置刷新界面的margin的值
- 最后通过对手指滑动过程中的位置来计算margin的值来对刷新界面进行操作。
- 当确定刷新的时候调用load方法进行数据的获取,数据获取完后再将刷新界面隐藏
- 最后将隐藏的方法在一个线程里面进行,能使它缓慢隐藏(这是抄自郭霖博客(≧▽≦))
里面有几个主要注意的点:判断margin的逻辑有点混乱,还有onTouch方法一定要返回false,因为一旦返回了true的话,会导致listView无法操作,我猜测listView内部应该也实现了OnTouchListener接口,当我们设置OnTouchListener的时候在它内部会对我们传入的listener的返回值进行判断,如果为true就不对listView本身的监听事件进行操作。
到这里基本上就完成了,其中由于没有新的Activity,也没有进行什么需要权限的操作,所以manifest文件里面的代码我们也不用修改。