如今油航,下拉刷新都已經成為App的標配了,哪個App會沒有下拉刷新呢怀浆?作為一個普通app用戶谊囚,想要去刷新內容,習慣性地就下拉一下执赡,已經成為用戶的一種習慣啦镰踏。作為一個開發(fā)者,想要集成下拉刷新沙合,直接就拷貝Android-PullToRefresh或者是android-Ultra-Pull-To-Refresh又或者其他下拉刷新庫奠伪,直接使用經典的下拉刷新UI,一個標志性的下拉箭頭首懈,一句經典的下拉以刷新更多绊率。一開始還好,慢慢的作為一個使用者而言究履,就覺得沒什么新意了滤否,作為一個開發(fā)者也覺得沒什么挑戰(zhàn)了。
那這樣挎袜,何不嘗試自己動手去實現(xiàn)一個真正屬于自己的下拉刷新庫呢顽聂?嘿嘿肥惭,那說干就干唄盯仪。那想想紊搪,現(xiàn)在下拉刷新的輪子那么多,我們也不可能從零開始寫全景,時間也不允許啊耀石,白天要上班寫業(yè)務代碼,回到宿舍還得重復別人寫過的代碼爸黄,而且自己寫的還派不上用場滞伟,因為已經有現(xiàn)成的,而且別人寫的東西那么多人用上了炕贵,踩過的坑肯定比你想的要多梆奈。雖然話說凡事要自己去實踐,理解的才會更深称开。說是這么說亩钟,但是我還是覺得站在巨人肩膀上,才會看得更遠鳖轰,嘿嘿......
正因為如此清酥,我才基于android-Ultra-Pull-To-Refresh實現(xiàn)一個很Q的笑臉下拉刷新,不吹不黑蕴侣,真的很Q哦焰轻。這個下拉大概的效果就是,下拉時昆雀,隨header高度變化而縮放辱志、轉眼睛,轉啊轉狞膘。松開時荸频,一個轉眼睛的笑臉加載動畫。
扯那么多客冈,我自己都覺得有點不耐煩了旭从。下面來簡要分析一下,怎么去實現(xiàn)一個個性化的下拉刷新庫场仲。以下是整個庫的目錄和悦,想不到吧,就僅僅三個類渠缕,一個布局文件鸽素。就可以實現(xiàn)你自己能想到的下拉效果。
下面貼出最主要相關的類
- PullToRefreshFaceView
/**
*
* @作 用:下拉刷新的笑臉
* @創(chuàng) 建 人: linguoding
* @日 期: 2016/3/9
*/
public class PullToRefreshFaceView extends View {
private Paint paint;//畫筆
private Paint eyePaint;
private int backgroupColor;
private int width;
private int height;
private int centerWidth;
private int centerHeight;
private float degrees;
private float radius = 0;
private float sweepRadius = 180;
private int radiusCircle;
private int eyeRadius;
private int eyeBallRadius;
private boolean isDrawFace = false;
AnimatorSet set = new AnimatorSet();
public PullToRefreshFaceView(Context context) {
super(context);
initView();
}
public PullToRefreshFaceView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoadingView);
backgroupColor = array.getColor(R.styleable.LoadingView_backgroupColor, Color.BLACK);
array.recycle();
initView();
}
public void setDegrees(float degrees) {
this.degrees = degrees;
invalidate();
}
public void setRadius(float radius) {
this.radius = radius;
invalidate();
}
public void setSweepRadius(float sweepRadius) {
this.sweepRadius = sweepRadius;
invalidate();
}
public void setRadiusCircle(int radiusCircle) {
this.radiusCircle = radiusCircle;
}
public void setEyeRadius(int eyeRadius) {
this.eyeRadius = eyeRadius;
}
public void setEyeBallRadius(int eyeBallRadius) {
this.eyeBallRadius = eyeBallRadius;
}
public void setDrawFace(boolean isDrawFace) {
this.isDrawFace = isDrawFace;
}
public int getRadiusCircle() {
return radiusCircle;
}
public int getEyeRadius() {
return eyeRadius;
}
public int getEyeBallRadius() {
return eyeBallRadius;
}
public boolean isDrawFace() {
return isDrawFace;
}
public void setBackgroupColor(int backgroupColor) {
this.backgroupColor = backgroupColor;
}
/**
* 刷新用的效果
*
* @param sunRadius
* @param per
*/
public void setPerView(int sunRadius, float per) {
if (per >= 0.5) {
isDrawFace = true;
} else {
isDrawFace = false;
}
per = Math.min(per, 1);
float tempRadius = sunRadius * per;
this.radiusCircle = (int) tempRadius;
invalidate();
}
private void initView() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(backgroupColor);
//眼眶畫筆
eyePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
eyePaint.setColor(Color.WHITE);
eyePaint.setStyle(Paint.Style.FILL);
createAnimatorSet();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/*super.onMeasure(widthMeasureSpec, heightMeasureSpec);*/
width = measureResult(widthMeasureSpec);
height = measureResult(heightMeasureSpec);
centerWidth = width >> 1;
centerHeight = height >> 1;
setMeasuredDimension(width, height);
}
private int measureResult(int widthMeasureSpec) {
int result = 0;
int sizeSpec = MeasureSpec.getSize(widthMeasureSpec);
int modeSpec = MeasureSpec.getMode(widthMeasureSpec);
if (modeSpec == MeasureSpec.EXACTLY) {
result = sizeSpec;
} else {
result = 400;
if (modeSpec == MeasureSpec.AT_MOST) {
result = Math.min(result, sizeSpec);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
centerWidth = width / 2;
centerHeight = height / 2;
canvas.translate(centerWidth, centerHeight);
radiusCircle = Math.min(centerWidth, centerHeight);
//畫圓
canvas.drawCircle(0, 0, radiusCircle, paint);
if (isDrawFace) {
//畫兩個眼框
eyeRadius = Math.min(centerWidth / 3, centerHeight / 3);
canvas.drawCircle(-centerWidth / 2, -centerHeight >> 3, eyeRadius, eyePaint);
canvas.drawCircle(centerWidth / 2, -centerHeight >> 3, eyeRadius, eyePaint);
//畫嘴巴
canvas.drawArc(new RectF(-eyeRadius, 0, eyeRadius, eyeRadius * 2), 0, 180, true, eyePaint);
canvas.save();
//畫兩個眼睛
eyeBallRadius = Math.min(centerWidth >> 3, centerHeight >> 3);
canvas.translate(-centerWidth / 2, -centerHeight >> 3);
canvas.rotate(-degrees);
canvas.drawCircle(0, (eyeRadius >> 1), eyeBallRadius, paint);
canvas.restore();
canvas.save();
canvas.translate(centerWidth / 2, -centerHeight >> 3);
canvas.rotate(-degrees);
canvas.drawCircle(0, (eyeRadius >> 1), eyeBallRadius, paint);
canvas.restore();
//畫兩個眼皮
canvas.save();
canvas.translate(-centerWidth / 2, -centerHeight >> 3);
canvas.drawArc(new RectF(-eyeRadius, -eyeRadius, eyeRadius, eyeRadius), -this.radius, -this.sweepRadius, false, paint);
canvas.restore();
canvas.save();
canvas.translate(centerWidth / 2, -centerHeight >> 3);
canvas.drawArc(new RectF(-eyeRadius, -eyeRadius, eyeRadius, eyeRadius), -this.radius, -this.sweepRadius, false, paint);
canvas.restore();
}
}
private void createAnimatorSet() {
ValueAnimator rotateAnimator = ValueAnimator.ofFloat(0, 360).setDuration(3000);
rotateAnimator.setInterpolator(new LinearInterpolator());
rotateAnimator.setRepeatCount(-1);
rotateAnimator.setEvaluator(new FloatEvaluator());
rotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
degrees = (float) animation.getAnimatedValue();
postInvalidate();
}
});
ValueAnimator translationAnimator = ValueAnimator.ofFloat(0, 90, 0).setDuration(3000);
translationAnimator.setInterpolator(new LinearInterpolator());
translationAnimator.setRepeatCount(-1);
translationAnimator.setEvaluator(new FloatEvaluator());
translationAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radius = (float) animation.getAnimatedValue();
postInvalidate();
}
});
ValueAnimator sweepAnimator = ValueAnimator.ofFloat(180, 0, 180).setDuration(3000);
sweepAnimator.setInterpolator(new LinearInterpolator());
sweepAnimator.setRepeatCount(-1);
sweepAnimator.setEvaluator(new FloatEvaluator());
sweepAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
sweepRadius = (float) animation.getAnimatedValue();
postInvalidate();
}
});
set.playTogether(rotateAnimator, translationAnimator, sweepAnimator);
}
public void startAnimators() {
set.start();
}
public void stopAnimators() {
set.cancel();
}
}
- FacePullToRefreshHeader 類
package com.pulltorefreshlibrary;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.pulltorefreshlibrary.view.PullToRefreshFaceView;
import java.text.SimpleDateFormat;
import java.util.Date;
import in.srain.cube.views.ptr.PtrFrameLayout;
import in.srain.cube.views.ptr.PtrUIHandler;
import in.srain.cube.views.ptr.indicator.PtrIndicator;
public class FacePullToRefreshHeader extends FrameLayout implements PtrUIHandler {
private final static String KEY_SharedPreferences = "face_ptr_classic_last_update";
private TextView mTitleTextView;
private PullToRefreshFaceView mLoadView;
private long mLastUpdateTime = -1;
private TextView mLastUpdateTextView;
private String mLastUpdateTimeKey;
private static SimpleDateFormat sDataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private boolean mShouldShowLastUpdate;
private LastUpdateTimeUpdater mLastUpdateTimeUpdater = new LastUpdateTimeUpdater();
public FacePullToRefreshHeader(Context context) {
super(context);
initViews(null);
}
public FacePullToRefreshHeader(Context context, AttributeSet attrs) {
super(context, attrs);
initViews(attrs);
}
public FacePullToRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViews(attrs);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mLastUpdateTimeUpdater != null) {
mLastUpdateTimeUpdater.stop();
}
}
private void initViews(AttributeSet attrs) {
View header = LayoutInflater.from(getContext()).inflate(R.layout.face_pull_to_refresh_header, this);
mTitleTextView = (TextView) header.findViewById(R.id.ptr_face_header_title);
mLastUpdateTextView = (TextView) header.findViewById(R.id.ptr_face_header_last_update);
mLoadView = (PullToRefreshFaceView) header.findViewById(R.id.ptr_load_view);
}
private void tryUpdateLastUpdateTime() {
if (TextUtils.isEmpty(mLastUpdateTimeKey) || !mShouldShowLastUpdate) {
mLastUpdateTextView.setVisibility(GONE);
} else {
String time = getLastUpdateTime();
if (TextUtils.isEmpty(time)) {
mLastUpdateTextView.setVisibility(GONE);
} else {
mLastUpdateTextView.setVisibility(VISIBLE);
mLastUpdateTextView.setText(time);
}
}
}
public void setLastUpdateTimeKey(String key) {
if (TextUtils.isEmpty(key)) {
return;
}
mLastUpdateTimeKey = key;
}
public void setLastUpdateTimeRelateObject(Object object) {
setLastUpdateTimeKey(object.getClass().getName());
}
/**
* 得到最后刷新時間
*
* @return
*/
private String getLastUpdateTime() {
if (mLastUpdateTime == -1 && !TextUtils.isEmpty(mLastUpdateTimeKey)) {
mLastUpdateTime = getContext().getSharedPreferences(KEY_SharedPreferences, 0).getLong(mLastUpdateTimeKey, -1);
}
if (mLastUpdateTime == -1) {
return null;
}
long diffTime = new Date().getTime() - mLastUpdateTime;
int seconds = (int) (diffTime / 1000);
if (diffTime < 0) {
return null;
}
if (seconds <= 0) {
return null;
}
StringBuilder sb = new StringBuilder();
sb.append(getContext().getString(R.string.ai_jia_ptr_last_update));
if (seconds < 60) {
sb.append(seconds + getContext().getString(in.srain.cube.views.ptr.R.string.cube_ptr_seconds_ago));
} else {
int minutes = (seconds / 60);
if (minutes > 60) {
int hours = minutes / 60;
if (hours > 24) {
Date date = new Date(mLastUpdateTime);
sb.append(sDataFormat.format(date));
} else {
sb.append(hours + getContext().getString(R.string.ai_jia_ptr_hours_ago));
}
} else {
sb.append(minutes + getContext().getString(R.string.ai_jia_ptr_minutes_ago));
}
}
return sb.toString();
}
private void resetView() {
//隱藏加載view和停止動畫
mLoadView.stopAnimators();
mLoadView.setVisibility(INVISIBLE);
}
/**
* 重置亦鳞,回到頂部的
*
* @param frame
*/
@Override
public void onUIReset(PtrFrameLayout frame) {
resetView();
mShouldShowLastUpdate = true;
tryUpdateLastUpdateTime();
}
/**
* 準備刷新馍忽,Header 將要出現(xiàn)時調用棒坏。
*
* @param frame
*/
@Override
public void onUIRefreshPrepare(PtrFrameLayout frame) {
mShouldShowLastUpdate = true;
tryUpdateLastUpdateTime();
mLoadView.setVisibility(VISIBLE);
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(getResources().getString(R.string.ai_jia_ptr_pull_down_to_refresh));
}
/**
* 開始刷新,Header 進入刷新狀態(tài)之前調用遭笋。
*
* @param frame
*/
@Override
public void onUIRefreshBegin(PtrFrameLayout frame) {
mShouldShowLastUpdate = false;
/**
* 讓loadView開始動畫
* */
mLoadView.startAnimators();
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(R.string.ai_jia_ptr_refreshing);
tryUpdateLastUpdateTime();
mLastUpdateTimeUpdater.stop();
}
@Override
public void onUIRefreshComplete(PtrFrameLayout frame) {
/*hideRotateView();
mProgressBar.setVisibility(INVISIBLE);*/
/*
* 加載完成坝冕,loadView停止動畫
* */
mLoadView.stopAnimators();
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(getResources().getString(R.string.ai_jia_ptr_refresh_complete));
// update last update time
SharedPreferences sharedPreferences = getContext().getSharedPreferences(KEY_SharedPreferences, 0);
if (!TextUtils.isEmpty(mLastUpdateTimeKey)) {
mLastUpdateTime = new Date().getTime();
sharedPreferences.edit().putLong(mLastUpdateTimeKey, mLastUpdateTime).commit();
}
}
/**
* 下拉過程中位置變化回調。
*
* @param frame
* @param isUnderTouch
* @param status
* @param ptrIndicator
*/
@Override
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
float percent = Math.min(1f, ptrIndicator.getCurrentPercent());//得到下拉過程的位置比例
if (status == PtrFrameLayout.PTR_STATUS_PREPARE) {
mLoadView.setDegrees(percent * 360 * 4);
ViewCompat.setScaleX(mLoadView, percent);
ViewCompat.setScaleY(mLoadView, percent);
mLoadView.setEyeRadius((int) (mLoadView.getEyeRadius()*percent));
mLoadView.setEyeBallRadius((int) (mLoadView.getEyeBallRadius()*percent));
mLoadView.setPerView(mLoadView.getRadiusCircle(), percent);
if (percent < 0.5) {
mLoadView.setRadius((percent * 180));
mLoadView.setSweepRadius((180 - percent * 360));
} else {
percent = (float) (percent - 0.5);
mLoadView.setRadius((90 - percent * 180));
mLoadView.setSweepRadius((percent * 360));
}
}
final int mOffsetToRefresh = frame.getOffsetToRefresh();
final int currentPos = ptrIndicator.getCurrentPosY();//當前位置
final int lastPos = ptrIndicator.getLastPosY();//上一個位置
if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {
if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
//下拉刷新
crossRotateLineFromBottomUnderTouch(frame);
}
} else if (currentPos > mOffsetToRefresh && lastPos <= mOffsetToRefresh) {
if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
//釋放刷新
crossRotateLineFromTopUnderTouch(frame);
}
}
}
/**
* 釋放刷新
*
* @param frame
*/
private void crossRotateLineFromTopUnderTouch(PtrFrameLayout frame) {
if (!frame.isPullToRefresh()) {
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(R.string.ai_jia_ptr_release_to_refresh);
}
}
/**
* 下拉刷新
*
* @param frame
*/
private void crossRotateLineFromBottomUnderTouch(PtrFrameLayout frame) {
mTitleTextView.setVisibility(VISIBLE);
if (frame.isPullToRefresh()) {
mTitleTextView.setText(getResources().getString(R.string.ai_jia_ptr_pull_down_to_refresh));
} else {
mTitleTextView.setText(getResources().getString(R.string.ai_jia_ptr_pull_down_to_refresh));
}
}
private class LastUpdateTimeUpdater implements Runnable {
private boolean mRunning = false;
private void start() {
if (TextUtils.isEmpty(mLastUpdateTimeKey)) {
return;
}
mRunning = true;
run();
}
private void stop() {
mRunning = false;
removeCallbacks(this);
}
@Override
public void run() {
tryUpdateLastUpdateTime();
if (mRunning) {
postDelayed(this, 1000);
}
}
}
}
- FacePullToRefreshLayout
public class FacePullToRefreshLayout extends PtrFrameLayout {
private FacePullToRefreshHeader mPullToRefreshHeader;
private RefreshListener refreshListener;
public FacePullToRefreshLayout(Context context) {
super(context);
initViews();
}
public FacePullToRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initViews();
}
public FacePullToRefreshLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initViews();
}
private void initViews() {
mPullToRefreshHeader = new FacePullToRefreshHeader(getContext());
setHeaderView(mPullToRefreshHeader);
addPtrUIHandler(mPullToRefreshHeader);
setPtrHandler(new PtrDefaultHandler() {
@Override
public void onRefreshBegin(PtrFrameLayout frame) {
if (refreshListener != null) {
refreshListener.onRefresh(frame);
}
}
});
}
public FacePullToRefreshHeader getHeader() {
return mPullToRefreshHeader;
}
public void setLastUpdateTimeKey(String key) {
if (mPullToRefreshHeader != null) {
mPullToRefreshHeader.setLastUpdateTimeKey(key);
}
}
public void setLastUpdateTimeRelateObject(Object object) {
if (mPullToRefreshHeader != null) {
mPullToRefreshHeader.setLastUpdateTimeRelateObject(object);
}
}
public interface RefreshListener {
void onRefresh(PtrFrameLayout frame);
}
public void setRefreshListener(RefreshListener refreshListener) {
this.refreshListener = refreshListener;
}
}
- 布局
<?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"
android:layout_width="match_parent"
android:layout_height="90dp"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<com.pulltorefreshlibrary.view.PullToRefreshFaceView
android:id="@+id/ptr_load_view"
android:layout_width="50dp"
android:layout_height="50dp"
app:backgroupColor="#88c6c6c6"
/>
<TextView
android:id="@+id/ptr_face_header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新..."
android:textColor="#666666"
android:textSize="12sp"/>
</LinearLayout>
<TextView
android:id="@+id/ptr_face_header_last_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="距離上次刷新:08秒之前"
android:textColor="#999999"
android:textSize="10sp"/>
</LinearLayout>
</LinearLayout>
想想瓦呼,還是放張圖片喂窟,比較有吸引力。
好啦央串,就到此為止吧磨澡,代碼不難,注釋寫得也清楚质和,所以就不說明了哈稳摄。主要還是因為我懶,以后再慢慢試著寫點分析饲宿,自己的思路什么的厦酬。最后,源碼放在Github上褒傅,覺得可以學到點東西的弃锐,start一下吧!點我殿托,點我吧