編輯:關於Android編程
package com.example.xrefreshview;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.view.WindowManager;
// Demo 下載地址 http://download.csdn.net/detail/u014540181/9587980
//android View 下拉上拉刷新 適合各種控件
//http://blog.csdn.net/u014540181 關注我的博客,相互交流,學習,進步,祝你生活工作愉快
//2016-07-27 19:45
public class MainActivity extends Activity {
private MeasureListView list_v;
private ScrollViewAdapter adapter;
private List list;
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
list = new ArrayList();
for (int i = 0; i < 5; i++) {
list.add("=="+i);
}
adapter = new ScrollViewAdapter(mContext, list);
list_v = (MeasureListView) findViewById(R.id.list_v);
list_v.setAdapter(adapter);
((PullToRefreshLayout) findViewById(R.id.refresh_view))
.setOnRefreshListener(new MyListener());
}
}
// MainActivity 的布局文件
package com.example.xrefreshview;
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* 自定義的布局,用來管理三個子控件,其中一個是下拉頭,一個是包含內容的pullableView(可以是實現Pullable接口的的任何View),
* 還有一個上拉頭,更多詳解見博客http://blog.csdn.net/zhongkejingwang/article/details/38868463
*
* @author 陳靖
*/
public class PullToRefreshLayout extends RelativeLayout
{
public static final String TAG = "PullToRefreshLayout";
// 初始狀態
public static final int INIT = 0;
// 釋放刷新
public static final int RELEASE_TO_REFRESH = 1;
// 正在刷新
public static final int REFRESHING = 2;
// 釋放加載
public static final int RELEASE_TO_LOAD = 3;
// 正在加載
public static final int LOADING = 4;
// 操作完畢
public static final int DONE = 5;
// 當前狀態
private int state = INIT;
// 刷新回調接口
private OnRefreshListener mListener;
// 刷新成功
public static final int SUCCEED = 0;
// 刷新失敗
public static final int FAIL = 1;
// 按下Y坐標,上一個事件點Y坐標
private float downY, lastY;
// 下拉的距離。注意:pullDownY和pullUpY不可能同時不為0
public float pullDownY = 0;
// 上拉的距離
private float pullUpY = 0;
// 釋放刷新的距離
private float refreshDist = 200;
// 釋放加載的距離
private float loadmoreDist = 200;
private MyTimer timer;
// 回滾速度
public float MOVE_SPEED = 8;
// 第一次執行布局
private boolean isLayout = false;
// 在刷新過程中滑動操作
private boolean isTouch = false;
// 手指滑動距離與下拉頭的滑動距離比,中間會隨正切函數變化
private float radio = 2;
// 下拉箭頭的轉180°動畫
private RotateAnimation rotateAnimation;
// 均勻旋轉動畫
private RotateAnimation refreshingAnimation;
// 下拉頭
private View refreshView;
// 下拉的箭頭
private View pullView;
// 正在刷新的圖標
private View refreshingView;
// 刷新結果圖標
private View refreshStateImageView;
// 刷新結果:成功或失敗
private TextView refreshStateTextView;
// 上拉頭
private View loadmoreView;
// 上拉的箭頭
private View pullUpView;
// 正在加載的圖標
private View loadingView;
// 加載結果圖標
private View loadStateImageView;
// 加載結果:成功或失敗
private TextView loadStateTextView;
// 實現了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) (8 + 5 * Math.tan(Math.PI / 2
/ getMeasuredHeight() * (pullDownY + Math.abs(pullUpY))));
if (!isTouch)
{
// 正在刷新,且沒有往上推的話則懸停,顯示"正在刷新..."
if (state == REFRESHING && pullDownY <= refreshDist)
{
pullDownY = refreshDist;
timer.cancel();
} else if (state == LOADING && -pullUpY <= loadmoreDist)
{
pullUpY = -loadmoreDist;
timer.cancel();
}
}
if (pullDownY > 0)
pullDownY -= MOVE_SPEED;
else if (pullUpY < 0)
pullUpY += MOVE_SPEED;
if (pullDownY < 0)
{
// 已完成回彈
pullDownY = 0;
pullView.clearAnimation();
// 隱藏下拉頭時有可能還在刷新,只有當前狀態不是正在刷新時才改變狀態
if (state != REFRESHING && state != LOADING)
changeState(INIT);
timer.cancel();
}
if (pullUpY > 0)
{
// 已完成回彈
pullUpY = 0;
pullUpView.clearAnimation();
// 隱藏下拉頭時有可能還在刷新,只有當前狀態不是正在刷新時才改變狀態
if (state != REFRESHING && state != LOADING)
changeState(INIT);
timer.cancel();
}
// 刷新布局,會自動調用onLayout
requestLayout();
}
};
public void setOnRefreshListener(OnRefreshListener listener)
{
mListener = listener;
}
public PullToRefreshLayout(Context context)
{
super(context);
initView(context);
}
public PullToRefreshLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
initView(context);
}
public PullToRefreshLayout(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initView(context);
}
private void initView(Context context)
{
timer = new MyTimer(updateHandler);
rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation(
context, R.anim.reverse_anim);
refreshingAnimation = (RotateAnimation) AnimationUtils.loadAnimation(
context, R.anim.rotating);
// 添加勻速轉動動畫
LinearInterpolator lir = new LinearInterpolator();
rotateAnimation.setInterpolator(lir);
refreshingAnimation.setInterpolator(lir);
}
private void hide()
{
timer.schedule(5);
}
/**
* 完成刷新操作,顯示刷新結果。注意:刷新完成後一定要調用這個方法
*/
/**
* @param refreshResult
* PullToRefreshLayout.SUCCEED代表成功,PullToRefreshLayout.FAIL代表失敗
*/
public void refreshFinish(int refreshResult)
{
refreshingView.clearAnimation();
refreshingView.setVisibility(View.GONE);
switch (refreshResult)
{
case SUCCEED:
// 刷新成功
refreshStateImageView.setVisibility(View.VISIBLE);
refreshStateTextView.setText(R.string.refresh_succeed);
refreshStateImageView
.setBackgroundResource(R.drawable.refresh_succeed);
break;
case FAIL:
default:
// 刷新失敗
refreshStateImageView.setVisibility(View.VISIBLE);
refreshStateTextView.setText(R.string.refresh_fail);
refreshStateImageView
.setBackgroundResource(R.drawable.refresh_failed);
break;
}
// 刷新結果停留1秒
new Handler()
{
@Override
public void handleMessage(Message msg)
{
changeState(DONE);
hide();
}
}.sendEmptyMessageDelayed(0, 1000);
}
/**
* 加載完畢,顯示加載結果。注意:加載完成後一定要調用這個方法
*
* @param refreshResult
* PullToRefreshLayout.SUCCEED代表成功,PullToRefreshLayout.FAIL代表失敗
*/
public void loadmoreFinish(int refreshResult)
{
loadingView.clearAnimation();
loadingView.setVisibility(View.GONE);
switch (refreshResult)
{
case SUCCEED:
// 加載成功
loadStateImageView.setVisibility(View.VISIBLE);
loadStateTextView.setText(R.string.load_succeed);
loadStateImageView.setBackgroundResource(R.drawable.load_succeed);
break;
case FAIL:
default:
// 加載失敗
loadStateImageView.setVisibility(View.VISIBLE);
loadStateTextView.setText(R.string.load_fail);
loadStateImageView.setBackgroundResource(R.drawable.load_failed);
break;
}
// 刷新結果停留1秒
new Handler()
{
@Override
public void handleMessage(Message msg)
{
changeState(DONE);
hide();
}
}.sendEmptyMessageDelayed(0, 1000);
}
private void changeState(int to)
{
state = to;
switch (state)
{
case INIT:
// 下拉布局初始狀態
refreshStateImageView.setVisibility(View.GONE);
refreshStateTextView.setText(R.string.pull_to_refresh);
pullView.clearAnimation();
pullView.setVisibility(View.VISIBLE);
// 上拉布局初始狀態
loadStateImageView.setVisibility(View.GONE);
loadStateTextView.setText(R.string.pullup_to_load);
pullUpView.clearAnimation();
pullUpView.setVisibility(View.VISIBLE);
break;
case RELEASE_TO_REFRESH:
// 釋放刷新狀態
refreshStateTextView.setText(R.string.release_to_refresh);
pullView.startAnimation(rotateAnimation);
break;
case REFRESHING:
// 正在刷新狀態
pullView.clearAnimation();
refreshingView.setVisibility(View.VISIBLE);
pullView.setVisibility(View.INVISIBLE);
refreshingView.startAnimation(refreshingAnimation);
refreshStateTextView.setText(R.string.refreshing);
break;
case RELEASE_TO_LOAD:
// 釋放加載狀態
loadStateTextView.setText(R.string.release_to_load);
pullUpView.startAnimation(rotateAnimation);
break;
case LOADING:
// 正在加載狀態
pullUpView.clearAnimation();
loadingView.setVisibility(View.VISIBLE);
pullUpView.setVisibility(View.INVISIBLE);
loadingView.startAnimation(refreshingAnimation);
loadStateTextView.setText(R.string.loading);
break;
case DONE:
// 刷新或加載完畢,啥都不做
break;
}
}
/**
* 不限制上拉或下拉
*/
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:
if (mEvents == 0)
{
if (((Pullable) pullableView).canPullDown() && canPullDown
&& state != LOADING)
{
// 可以下拉,正在加載時不能下拉
// 對實際滑動距離做縮小,造成用力拉的感覺
pullDownY = pullDownY + (ev.getY() - lastY) / radio;
if (pullDownY < 0)
{
pullDownY = 0;
canPullDown = false;
canPullUp = true;
}
if (pullDownY > getMeasuredHeight())
pullDownY = getMeasuredHeight();
if (state == REFRESHING)
{
// 正在刷新的時候觸摸移動
isTouch = true;
}
} else if (((Pullable) pullableView).canPullUp() && canPullUp
&& state != REFRESHING)
{
// 可以上拉,正在刷新時不能上拉
pullUpY = pullUpY + (ev.getY() - lastY) / radio;
if (pullUpY > 0)
{
pullUpY = 0;
canPullDown = true;
canPullUp = false;
}
if (pullUpY < -getMeasuredHeight())
pullUpY = -getMeasuredHeight();
if (state == LOADING)
{
// 正在加載的時候觸摸移動
isTouch = true;
}
} else
releasePull();
} else
mEvents = 0;
lastY = ev.getY();
// 根據下拉距離改變比例
radio = (float) (2 + 2 * Math.tan(Math.PI / 2 / getMeasuredHeight()
* (pullDownY + Math.abs(pullUpY))));
requestLayout();
if (pullDownY <= refreshDist && state == RELEASE_TO_REFRESH)
{
// 如果下拉距離沒達到刷新的距離且當前狀態是釋放刷新,改變狀態為下拉刷新
changeState(INIT);
}
if (pullDownY >= refreshDist && state == INIT)
{
// 如果下拉距離達到刷新的距離且當前狀態是初始狀態刷新,改變狀態為釋放刷新
changeState(RELEASE_TO_REFRESH);
}
// 下面是判斷上拉加載的,同上,注意pullUpY是負值
if (-pullUpY <= loadmoreDist && state == RELEASE_TO_LOAD)
{
changeState(INIT);
}
if (-pullUpY >= loadmoreDist && state == INIT)
{
changeState(RELEASE_TO_LOAD);
}
// 因為刷新和加載操作不能同時進行,所以pullDownY和pullUpY不會同時不為0,因此這裡用(pullDownY +
// Math.abs(pullUpY))就可以不對當前狀態作區分了
if ((pullDownY + Math.abs(pullUpY)) > 8)
{
// 防止下拉過程中誤觸發長按事件和點擊事件
ev.setAction(MotionEvent.ACTION_CANCEL);
}
break;
case MotionEvent.ACTION_UP:
if (pullDownY > refreshDist || -pullUpY > loadmoreDist)
// 正在刷新時往下拉(正在加載時往上拉),釋放後下拉頭(上拉頭)不隱藏
isTouch = false;
if (state == RELEASE_TO_REFRESH)
{
changeState(REFRESHING);
// 刷新操作
if (mListener != null)
mListener.onRefresh(this);
} else if (state == RELEASE_TO_LOAD)
{
changeState(LOADING);
// 加載操作
if (mListener != null)
mListener.onLoadMore(this);
}
hide();
default:
break;
}
// 事件分發交給父類
super.dispatchTouchEvent(ev);
return true;
}
private void initView()
{
// 初始化下拉布局
pullView = refreshView.findViewById(R.id.pull_icon);
refreshStateTextView = (TextView) refreshView
.findViewById(R.id.state_tv);
refreshingView = refreshView.findViewById(R.id.refreshing_icon);
refreshStateImageView = refreshView.findViewById(R.id.state_iv);
// 初始化上拉布局
pullUpView = loadmoreView.findViewById(R.id.pullup_icon);
loadStateTextView = (TextView) loadmoreView
.findViewById(R.id.loadstate_tv);
loadingView = loadmoreView.findViewById(R.id.loading_icon);
loadStateImageView = loadmoreView.findViewById(R.id.loadstate_iv);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
if (!isLayout)
{
// 這裡是第一次進來的時候做一些初始化
refreshView = getChildAt(0);
pullableView = getChildAt(1);
loadmoreView = getChildAt(2);
isLayout = true;
initView();
refreshDist = ((ViewGroup) refreshView).getChildAt(0)
.getMeasuredHeight();
loadmoreDist = ((ViewGroup) loadmoreView).getChildAt(0)
.getMeasuredHeight();
}
// 改變子控件的布局,這裡直接用(pullDownY + pullUpY)作為偏移量,這樣就可以不對當前狀態作區分
refreshView.layout(0,
(int) (pullDownY + pullUpY) - refreshView.getMeasuredHeight(),
refreshView.getMeasuredWidth(), (int) (pullDownY + pullUpY));
pullableView.layout(0, (int) (pullDownY + pullUpY),
pullableView.getMeasuredWidth(), (int) (pullDownY + pullUpY)
+ pullableView.getMeasuredHeight());
loadmoreView.layout(0,
(int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight(),
loadmoreView.getMeasuredWidth(),
(int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight()
+ loadmoreView.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();
}
}
}
/**
* 刷新加載回調接口
*
* @author chenjing
*
*/
public interface OnRefreshListener
{
/**
* 刷新操作
*/
void onRefresh(PullToRefreshLayout pullToRefreshLayout);
/**
* 加載操作
*/
void onLoadMore(PullToRefreshLayout pullToRefreshLayout);
}
}
package com.example.xrefreshview;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
public class PullableScrollView extends ScrollView implements Pullable
{
public PullableScrollView(Context context)
{
super(context);
}
public PullableScrollView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public PullableScrollView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public boolean canPullDown()
{
if (getScrollY() == 0)
return true;
else
return false;
}
public boolean canPullUp()
{
if (getScrollY() >= (getChildAt(0).getHeight() - getMeasuredHeight()))
return true;
else
return false;
}
}
package com.example.xrefreshview;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;
/**
* 測量高度listview
* @ClassName: MeasureListView
* @Description: TODO
* @author Comsys-Administrator
* @date 2015-6-7 下午6:31:36
* 要是 ListView 放在 ScrollView 裡面使用,就這樣寫
*/
public class MeasureListView extends ListView {
public MeasureListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); // TODO Auto-generated constructor stub
}
public MeasureListView(Context context, AttributeSet attrs) {
super(context, attrs); // TODO Auto-generated constructor stub
}
public MeasureListView(Context context) {
super(context); // TODO Auto-generated constructor stub
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int hSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, hSpec);
}
}
package com.example.xrefreshview;
public interface Pullable
{
/**
* 判斷是否可以下拉,如果不需要下拉功能可以直接return false
*
* @return true如果可以下拉否則返回false
*/
boolean canPullDown();
/**
* 判斷是否可以上拉,如果不需要上拉功能可以直接return false
*
* @return true如果可以上拉否則返回false
*/
boolean canPullUp();
}
package com.example.xrefreshview;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class ScrollViewAdapter extends BaseAdapter{
private LayoutInflater layoutInflater;
private Context mContext;
private List list;
private ViewHolder viewHolder;
public ScrollViewAdapter(Context mContext,List list){
this.mContext = mContext;
this.layoutInflater = LayoutInflater.from(mContext);
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
viewHolder = new ViewHolder();
convertView = layoutInflater.inflate(R.layout.item_adapter, null);
viewHolder.itemTv = (TextView) convertView.findViewById(R.id.tex_m);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.itemTv.setText(list.get(position));
return convertView;
}
class ViewHolder{
TextView itemTv;
}
}
// ScrollViewAdapter 的布局文件
package com.example.xrefreshview;
import com.example.xrefreshview.PullToRefreshLayout.OnRefreshListener;
import android.os.Handler;
import android.os.Message;
public class MyListener implements OnRefreshListener
{
public void onRefresh(final PullToRefreshLayout pullToRefreshLayout)
{
// 下拉刷新操作
new Handler()
{
@Override
public void handleMessage(Message msg)
{
// 千萬別忘了告訴控件刷新完畢了哦!
pullToRefreshLayout.refreshFinish(PullToRefreshLayout.SUCCEED);
}
}.sendEmptyMessageDelayed(0, 5000);
}
public void onLoadMore(final PullToRefreshLayout pullToRefreshLayout)
{
// 加載操作
new Handler()
{
@Override
public void handleMessage(Message msg)
{
// 千萬別忘了告訴控件加載完畢了哦!
pullToRefreshLayout.loadmoreFinish(PullToRefreshLayout.SUCCEED);
}
}.sendEmptyMessageDelayed(0, 5000);
}
}
// 在res下面建個 anim 文件夾 把這個文件放進去
// 在res下面建個 anim 文件夾 把這個文件放進去
通用版下拉刷新上拉加載控件 這裡是HeadView 下拉刷新 釋放立即刷新 正在刷新... 刷新成功 刷新失敗 上拉加載更多 釋放立即加載 正在加載... 加載成功 加載失敗
#FFFFFF #000000 #aaaaaa #6593cb
Android學習筆記二十五之ListView多布局實現
Android學習筆記二十五之ListView多布局實現 這一節是介紹ListView這個控件的最後一節,實現一個Item的多布局。像我們經常在用的各種即時通訊工具,Q
笑談Android圖表------MPAndroidCharts
MPAndroidChart是一款基於Android的開源圖表庫,MPAndroidChart不僅可以在Android設備上繪制各種統計圖表,而且可以對圖表進行拖動和縮放
從源碼角度分析Android 事件傳遞流程
自從開始負責公共控件模塊開始,我一直都想好好分析一下Android事件傳遞流程,相信網上有一大堆相關文章,但是我個人覺得作為一個專業的控件開發人員,如果只是知道一下大概,
安卓快速入門指南(中)
第3節 計算器小應用現在起,我們就開始正式開發“計算器”應用。3.1 計算器界面布局這一節,我們將完成計算器的界面布局,讓它初具計算器的模樣。計算