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

android 仿qq5.3,下拉刷新,自动加载更多,下拉回弹,仿IOS橡皮筋效果,通用版,效果完美

2015-01-20 10:48 1486 查看
目录(?)[-]

下面看修改后的PullToRefreshLayout代码
用法也很简单
加载完成后自己设置true或者false就可以了
后来又发现QQ53每一个界面貌似都可以下拉回弹这样的设计很人性化模仿的IOS的下拉回弹橡皮筋效果于是自己又借助上面的代码修改了下做成了和QQ一样的效果OverScrollView
上面注意的是我加了两个接口函数一个是可以在每次滑动的时候触发一个只是在头部和尾部的时候触发
其实利用这两个接口就可以实现QQ空间或者新浪个人中心那个下拉刷新头像伸缩的效果
上效果
用法很简单和Scrollview一样Scollview怎么用这个就怎么用把Scrollview换成OverScollview就可以了
这个就可以了写的比较粗糙有啥问题请留言
好了最后提供下demo
httpdownloadcsdnnetdetailwondaymh8314885

最近在做项目,用到了下拉刷新,本来这个效果应该是再普通不过的一个效果,自己懒得去写,所以就去网上找,找了很多很多,发现效果都不是很理想,要么不支持多点触控,要么不支持自动加载更多,一直觉得QQ5.3的下拉刷新是很人性化了,所以一直想找这样的下拉刷新和自动加载更多,找了很久才发现一个和QQ5.3接近的效果,http://blog.csdn.net/zhongkejingwang/article/details/38868463,大家可以去看下他的博客,不过这个拿过来貌似有一些bug,自动加载更多会出现bug,而且不能自己控制是否还有更多数据。经过我的修改已经可以自己随意控制是否还有更多数据。

先上图:









下面看修改后的PullToRefreshLayout代码:

[java] view
plaincopy

import android.content.Context;

import android.graphics.drawable.AnimationDrawable;

import android.util.AttributeSet;

import android.view.LayoutInflater;

import android.view.MotionEvent;

import android.view.View;

import android.widget.ImageView;

import android.widget.ListView;

import android.widget.TextView;



/**

* 如果不需要下拉刷新直接在canPullDown中返回false,这里的自动加载和下拉刷新没有冲突,通过增加在尾部的footerview实现自动加载,

* 所以在使用中不要再动footerview了

* 更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38963177

* @author chenjing

*

*/

public class PullableListView extends ListView implements Pullable

{

public static final int INIT = 0;

public static final int LOADING = 1;

public static final int NO_MORE_DATA = 2;

private OnLoadListener mOnLoadListener;

private View mLoadmoreView;

private ImageView mLoadingView;

private TextView mStateTextView;

private int state = INIT;

private boolean canLoad = true;

private boolean autoLoad = true;

private boolean hasMoreData = true;

private AnimationDrawable mLoadAnim;



public PullableListView(Context context)

{

super(context);

init(context);

}



public PullableListView(Context context, AttributeSet attrs)

{

super(context, attrs);

init(context);

}



public PullableListView(Context context, AttributeSet attrs, int defStyle)

{

super(context, attrs, defStyle);

init(context);

}



private void init(Context context)

{

mLoadmoreView = LayoutInflater.from(context).inflate(R.layout.load_more,

null);

mLoadingView = (ImageView) mLoadmoreView.findViewById(R.id.loading_icon);

mLoadingView.setBackgroundResource(R.anim.loading_anim);

mLoadAnim = (AnimationDrawable) mLoadingView.getBackground();

mStateTextView = (TextView) mLoadmoreView.findViewById(R.id.loadstate_tv);

mLoadmoreView.setOnClickListener(new OnClickListener()

{



@Override

public void onClick(View v)

{

//点击加载

if(state != LOADING && hasMoreData){

load();

}

}

});

addFooterView(mLoadmoreView, null, false);

}



/**

* 是否开启自动加载

* @param enable true启用,false禁用

*/

public void enableAutoLoad(boolean enable){

autoLoad = enable;

}



/**

* 是否显示加载更多

* @param v true显示,false不显示

*/

public void setLoadmoreVisible(boolean v){

if(v)

{

if (getFooterViewsCount() == 0) {

addFooterView(mLoadmoreView, null, false);

}

}

else {

removeFooterView(mLoadmoreView);

}

}



@Override

public boolean onTouchEvent(MotionEvent ev)

{

switch (ev.getActionMasked())

{

case MotionEvent.ACTION_DOWN:

// 按下的时候禁止自动加载

canLoad = false;

break;

case MotionEvent.ACTION_UP:

// 松开手判断是否自动加载

canLoad = true;

checkLoad();

break;

}

return super.onTouchEvent(ev);

}



@Override

protected void onScrollChanged(int l, int t, int oldl, int oldt)

{

super.onScrollChanged(l, t, oldl, oldt);

// 在滚动中判断是否满足自动加载条件

checkLoad();

}



/**

* 判断是否满足自动加载条件

*/

private void checkLoad()

{

if (reachBottom() && mOnLoadListener != null && state != LOADING

&& canLoad && autoLoad && hasMoreData)

load();

}



private void load(){

changeState(LOADING);

mOnLoadListener.onLoad(this);

}



private void changeState(int state)

{

this.state = state;

switch (state)

{

case INIT:

mLoadAnim.stop();

mLoadingView.setVisibility(View.INVISIBLE);

mStateTextView.setText(R.string.more);

break;



case LOADING:

mLoadingView.setVisibility(View.VISIBLE);

mLoadAnim.start();

mStateTextView.setText(R.string.loading);

break;



case NO_MORE_DATA:

mLoadAnim.stop();

mLoadingView.setVisibility(View.INVISIBLE);

mStateTextView.setText("没有更多的数据了");

break;

}

}



/**

* 完成加载

*/

public void finishLoading()

{

changeState(INIT);

}



@Override

public boolean canPullDown()

{

if (getCount() == 0)

{

// 没有item的时候也可以下拉刷新

return true;

} else if (getFirstVisiblePosition() == 0

&& getChildAt(0).getTop() >= 0)

{

// 滑到ListView的顶部了

return true;

} else

return false;

}



public void setOnLoadListener(OnLoadListener listener)

{

this.mOnLoadListener = listener;

}



/**

* @return footerview可见时返回true,否则返回false

*/

private boolean reachBottom()

{

if (getCount() == 0)

{

return true;

} else if (getLastVisiblePosition() == (getCount() - 1))

{

// 滑到底部,且頂部不是第0个,也就是说item数超过一屏后才能自动加载,否则只能点击加载

if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null

&& getChildAt(

getLastVisiblePosition()

- getFirstVisiblePosition()).getTop() < getMeasuredHeight() && !canPullDown())

return true;

}

return false;

}



public boolean isHasMoreData() {

return hasMoreData;

}



public void setHasMoreData(boolean hasMoreData) {

this.hasMoreData = hasMoreData;

if (!hasMoreData) {

changeState(NO_MORE_DATA);

}else{

changeState(INIT);

}

}



public interface OnLoadListener

{

void onLoad(PullableListView pullableListView);

}



@Override

public boolean canPullUp() {

// TODO Auto-generated method stub

return false;

}

}

用法也很简单:

[java] view
plaincopy

pullToRefreshLayout.refreshFinish(PullToRefreshLayout.SUCCEED);

listView.setHasMoreData(true);//是否有更多数据

加载完成后自己设置true或者false就可以了!

后来又发现QQ5.3每一个界面貌似都可以下拉回弹,这样的设计很人性化,模仿的IOS的下拉回弹橡皮筋效果,于是自己又借助上面的代码修改了下做成了和QQ一样的效果:OverScrollView

[java] view
plaincopy

/**

* 更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38868463

*

* @author 陈靖

*/

public class OverScrollView extends ScrollView {

public static final String TAG = "PullToRefreshLayout";

//触发事件的高度默认阀值

private static final int TRIGGER_HEIGHT = 120;

//滑动的总距离

private float overScrollDistance;

//触发事件的高度阀值,最小值为30

private int mOverScrollTrigger = TRIGGER_HEIGHT;

private OverScrollTinyListener mOverScrollTinyListener;

private OverScrollListener mOverScrollListener;

// 按下Y坐标,上一个事件点Y坐标

private float downY, lastY;

// 下拉的距离。注意:pullDownY和pullUpY不可能同时不为0

public float pullDownY = 0;

// 上拉的距离

private float pullUpY = 0;

private MyTimer timer;

// 回滚速度

public float MOVE_SPEED = 8;

// 第一次执行布局

private boolean isLayout = false;

// 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化

private float radio = 2;

// 实现了Pullable接口的View

private View pullableView;

// 过滤多点触碰

private int mEvents;

// 这两个变量用来控制pull的方向,如果不加控制,当情况满足可上拉又可下拉时没法下拉

private boolean canPullDown = true;

private boolean canPullUp = true;



/**

* 执行自动回滚的handler

*/

Handler updateHandler = new Handler() {



@Override

public void handleMessage(Message msg) {

// 回弹速度随下拉距离moveDeltaY增大而增大

MOVE_SPEED = (float) (5 + 15 * Math.tan(Math.PI / 2

/ getMeasuredHeight() * (pullDownY + Math.abs(pullUpY))));

if (pullDownY > 0)

pullDownY -= MOVE_SPEED;

else if (pullUpY < 0)

pullUpY += MOVE_SPEED;

if (pullDownY < 0) {

// 已完成回弹

pullDownY = 0;

timer.cancel();

}

if (pullUpY > 0) {

// 已完成回弹

pullUpY = 0;

timer.cancel();

}

// 刷新布局,会自动调用onLayout

requestLayout();

}



};



public OverScrollView(Context context) {

super(context);

initView(context);

}



public OverScrollView(Context context, AttributeSet attrs) {

super(context, attrs);

initView(context);

}



public OverScrollView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

initView(context);

}



private void initView(Context context) {

timer = new MyTimer(updateHandler);

}



private void hide() {

timer.schedule(5);

}



/**

* 不限制上拉或下拉

*/

private void releasePull() {

canPullDown = true;

canPullUp = true;

}



/*

* (非 Javadoc)由父控件决定是否分发事件,防止事件冲突

*

* @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent)

*/

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

switch (ev.getActionMasked()) {

case MotionEvent.ACTION_DOWN:

downY = ev.getY();

lastY = downY;

timer.cancel();

mEvents = 0;

releasePull();

break;

case MotionEvent.ACTION_POINTER_DOWN:

case MotionEvent.ACTION_POINTER_UP:

// 过滤多点触碰

mEvents = -1;

break;

case MotionEvent.ACTION_MOVE:

float deltaY = ev.getY() - lastY;

if (mEvents == 0) {

if (canPullDown && isCanPullDown()) {

// 可以下拉,正在加载时不能下拉

// 对实际滑动距离做缩小,造成用力拉的感觉

pullDownY = pullDownY + deltaY / radio;

if (ev.getY() - lastY < 0) {

pullDownY = pullDownY + deltaY ;

}

if (pullDownY < 0) {

pullDownY = 0;

canPullDown = false;

canPullUp = true;

}

if (pullDownY > getMeasuredHeight())

pullDownY = getMeasuredHeight();

overScrollDistance = pullDownY;

} else if (canPullUp && isCanPullUp()) {

// 可以上拉,正在刷新时不能上拉

pullUpY = pullUpY + deltaY / radio;

if (ev.getY() - lastY > 0) {

pullUpY = pullUpY + deltaY ;

}

if (pullUpY > 0) {

pullUpY = 0;

canPullDown = true;

canPullUp = false;

}

if (pullUpY < -getMeasuredHeight())

pullUpY = -getMeasuredHeight();

overScrollDistance = pullUpY;

} else

releasePull();

} else

mEvents = 0;

lastY = ev.getY();

// 根据下拉距离改变比例

radio = (float) (2 + 3 * Math.tan(Math.PI / 2 / getMeasuredHeight()

* (pullDownY + Math.abs(pullUpY))));

requestLayout();

// 因为刷新和加载操作不能同时进行,所以pullDownY和pullUpY不会同时不为0,因此这里用(pullDownY +

// Math.abs(pullUpY))就可以不对当前状态作区分了

if ((pullDownY + Math.abs(pullUpY)) > 8) {

// 防止下拉过程中误触发长按事件和点击事件

ev.setAction(MotionEvent.ACTION_CANCEL);

}

if(mOverScrollTinyListener != null){

mOverScrollTinyListener.scrollDistance((int)deltaY, (int)overScrollDistance);

}

break;

case MotionEvent.ACTION_UP:

hide();

overScrollTrigger();

if(mOverScrollTinyListener != null && (isCanPullDown() || isCanPullUp())){

mOverScrollTinyListener.scrollLoosen();

}

break;

case MotionEvent.ACTION_CANCEL:

if(mOverScrollTinyListener != null && (isCanPullDown() || isCanPullUp())){

mOverScrollTinyListener.scrollLoosen();

}

break;

default:

break;

}

// 事件分发交给父类

try {

super.dispatchTouchEvent(ev);

} catch (Exception e) {

// TODO: handle exception

e.printStackTrace();

}

return true;

}





@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

if (!isLayout) {

// 这里是第一次进来的时候做一些初始化

pullableView = getChildAt(0);

isLayout = true;

}



pullableView.layout(0, (int) (pullDownY + pullUpY),

pullableView.getMeasuredWidth(), (int) (pullDownY + pullUpY)

+ pullableView.getMeasuredHeight());

}



class MyTimer {

private Handler handler;

private Timer timer;

private MyTask mTask;



public MyTimer(Handler handler) {

this.handler = handler;

timer = new Timer();

}



public void schedule(long period) {

if (mTask != null) {

mTask.cancel();

mTask = null;

}

mTask = new MyTask(handler);

timer.schedule(mTask, 0, period);

}



public void cancel() {

if (mTask != null) {

mTask.cancel();

mTask = null;

}

}



class MyTask extends TimerTask {

private Handler handler;



public MyTask(Handler handler) {

this.handler = handler;

}



@Override

public void run() {

handler.obtainMessage().sendToTarget();

}



}

}



/**

* 判断是否滚动到顶部

*/

private boolean isCanPullDown() {

return getScrollY() == 0 ||

pullableView.getHeight() < getHeight() + getScrollY();

}



/**

* 判断是否滚动到底部

*/

private boolean isCanPullUp() {

return pullableView.getHeight() <= getHeight() + getScrollY();

}



private boolean isOnTop(){

return getScrollY() == 0;

}



private boolean isOnBottom(){

return getScrollY() + getHeight() == pullableView.getHeight();

}



/**

* 当OverScroll超出一定值时,调用此监听

*

* @author King

* @since 2014-4-9 下午4:36:29

*/

public interface OverScrollListener {



/**

* 顶部

*/

void headerScroll();



/**

* 底部

*/

void footerScroll();



}



/**

* 每当OverScroll时,都能触发的监听

* @author King

* @since 2014-4-9 下午4:39:06

*/

public interface OverScrollTinyListener{



/**

* 滚动距离

* @param tinyDistance 当前滚动的细小距离

* @param totalDistance 滚动的总距离

*/

void scrollDistance(int tinyDistance, int totalDistance);



/**

* 滚动松开

*/

void scrollLoosen();

}



/**

* 设置OverScrollListener出发阀值

* @param height

*/

public void setOverScrollTrigger(int height){

if(height >= 30){

mOverScrollTrigger = height;

}

}



private void overScrollTrigger(){

if(mOverScrollListener == null){

return;

}



if(overScrollDistance > mOverScrollTrigger && overScrollDistance >= 0){

mOverScrollListener.headerScroll();

}



if(overScrollDistance < -mOverScrollTrigger && overScrollDistance < 0){

mOverScrollListener.footerScroll();

}

}



public OverScrollTinyListener getOverScrollTinyListener() {

return mOverScrollTinyListener;

}



public void setOverScrollTinyListener(OverScrollTinyListener OverScrollTinyListener) {

this.mOverScrollTinyListener = OverScrollTinyListener;

}



public OverScrollListener getOverScrollListener() {

return mOverScrollListener;

}



public void setOverScrollListener(OverScrollListener OverScrollListener) {

this.mOverScrollListener = OverScrollListener;

}

}

上面注意的是我加了两个接口函数,一个是可以在每次滑动的时候触发,一个只是在头部和尾部的时候触发。

其实利用这两个接口就可以实现QQ空间,或者新浪个人中心那个下拉刷新头像伸缩的效果。

上效果:







用法很简单和Scrollview一样,Scollview怎么用这个就怎么用,把Scrollview换成OverScollview就可以了

OverScrollView由于继承了ScrollView那么自然里面不能嵌套listview和其ExpandListView,如果非要嵌套用,那也可以,使你的listview或者expandListview继承

[java] view
plaincopy

import android.content.Context;

import android.util.AttributeSet;

import android.widget.ExpandableListView;

import android.widget.ListView;



public class CustomerListView extends ListView {



public CustomerListView(Context context, AttributeSet attrs) {

super(context, attrs);

// TODO Auto-generated constructor stub

}



@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

// TODO Auto-generated method stub

int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);



super.onMeasure(widthMeasureSpec, expandSpec);

}

}

这个就可以了。写的比较粗糙,有啥问题请留言!

好了最后提供下demo:

http://download.csdn.net/detail/wondaymh/8314885

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐