前言
最近“兜礼”APP项目,产品需要的下拉刷新效果完全和ios方一致,参考了很多的框架,感觉实现出来的效果并不是很好,最后选择了PtrUI这个框架来实现先关的效果,感觉也还不错。
需求需要的效果
下拉效果
加载过程效果
如何实现该效果
小编在使用ULtraPTr框架的时候,这里开发很多工作并行,顾只是使用了这一框架,截断了其中设置,换了布局完成了如下效果,当然动画具体实现等还是自己写的哈
一.Activity使用
1.布局webview_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/comm_bg"
android:orientation="vertical">
<include
android:id="@+id/layout_top_bar"
layout="@layout/top_bar" />
<com.reach.doooly.pullresh.PtrClassicFrameLayout xmlns:cube_ptr="http://schemas.android.com/apk/res-auto"
android:id="@+id/view_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/layout_top_bar">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<WebView
android:id="@+id/view_webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/view_progressbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="1dp"
android:progressDrawable="@drawable/pull_progressbar_pg"
android:visibility="gone" />
</RelativeLayout>
<include layout="@layout/webview_err_view"
android:visibility="gone"/>
</RelativeLayout>
</com.reach.doooly.pullresh.PtrClassicFrameLayout>
</RelativeLayout>
2.activity实现
package com.reach.doooly.ui.mywrite;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.provider.SyncStateContract;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import com.bumptech.glide.Glide;
import com.reach.doooly.R;
import com.reach.doooly.application.RHApplication;
import com.reach.doooly.base.activity.AppManager;
import com.reach.doooly.base.activity.RHFragmentActivty;
import com.reach.doooly.base.activity.ToastTools;
import com.reach.doooly.base.listener.FragmentListener;
import com.reach.doooly.base.log.Logs;
import com.reach.doooly.bean.WeachPayBeanVo;
import com.reach.doooly.permission.PermissionFail;
import com.reach.doooly.permission.PermissionSuccess;
import com.reach.doooly.permission.PermissionUtil;
import com.reach.doooly.pullresh.PtrClassicFrameLayout;
import com.reach.doooly.pullresh.PtrDefaultHandler;
import com.reach.doooly.pullresh.PtrFrameLayout;
import com.reach.doooly.pullresh.PtrHandler;
import com.reach.doooly.utils.StringUtlis;
import com.reach.doooly.pullresh.webview.WebViewManage;
import com.reach.doooly.pullresh.webview.WebViewManage.WebViewManageListener;
import com.reach.doooly.pullresh.webview.NativePlugin;
import com.reach.doooly.pullresh.top.TopBarSetListener;
import com.reach.doooly.pullresh.top.WebTopBarManage;
import com.reach.doooly.utils.bitmap.BitmapDecodeUtil;
import com.reach.doooly.utils.bitmap.BitmapUtil;
import com.reach.doooly.utils.bitmap.Constants;
import com.reach.doooly.utils.bitmap.CqcCameraUtil;
import com.reach.doooly.wxapi.WXEntryActivity;
import com.tencent.mm.sdk.constants.ConstantsAPI;
import com.tencent.mm.sdk.modelbase.BaseReq;
import com.tencent.mm.sdk.modelbase.BaseResp;
import com.tencent.mm.sdk.modelmsg.SendMessageToWX;
import com.tencent.mm.sdk.modelmsg.WXMediaMessage;
import com.tencent.mm.sdk.modelmsg.WXWebpageObject;
import com.tencent.mm.sdk.modelpay.PayReq;
import com.tencent.mm.sdk.openapi.IWXAPI;
import com.tencent.mm.sdk.openapi.IWXAPIEventHandler;
import com.tencent.mm.sdk.openapi.WXAPIFactory;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
/**
* @author qinming.fu@reach-core.com
* <p>
* webView界面
*/
public class WebViewActivity extends Activty {
private String TAG = "WebViewActivity";
public static final String NAME_URL = "URL";
public static boolean isShow = false;
private WebViewActivity activity;
private String url = "";
///Frame
private PtrClassicFrameLayout mPtrFrame;
private ProgressBar progressBar;//loading
private WebView webView;//webView
//头部和webview的管理器
private WebTopBarManage topBarManage;
private WebViewManage webViewManage;
private NativePlugin nativePlugin;
protected FragmentListener fragmentListener;
private RelativeLayout error_rela_layout;
protected int getContentLayout() {
return R.layout.webview_layout;
}
@Override
protected void initView() {
activity = this;
isShow = true;
RHApplication.getInstance().registerLocationReceiver(activity);
setLeftButton(true, "return", "返回", "RHNativeJS.goLastPage()");
error_rela_layout = (RelativeLayout) findViewById(R.id.error_rela_layout);
mPtrFrame = (PtrClassicFrameLayout) findViewById(R.id.view_frame);
webView = (WebView) findViewById(R.id.view_webview);
progressBar = (ProgressBar) findViewById(R.id.view_progressbar);
setRefushFragme();
/**配置WebView*/
nativePlugin = new NativePlugin(activity, webView);
nativePlugin.setTopBarSetListener(this);
webViewManage = new WebViewManage(activity, topBarManage, webView, progressBar, error_rela_layout, nativePlugin);
//不添加url拦截器
webViewManage.setListener(this);
}
@Override
protected void initData() {
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
url = bundle.getString("URL");
url = "file:///android_asset/index.html";//测试tabbar
Logs.d(TAG, "url:" + url);
webView.loadUrl(url);
}
}
/**
* 截获硬键盘返回事件
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
// 页面返回
String leftJsFunc = topBarManage.getLeftJsFunc();
if (!StringUtlis.isEmpty(leftJsFunc)) {
if (!isFastDoubleClick()) {
webView.loadUrl("javascript:" + leftJsFunc);
}
return true;
}
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onDestroy() {
super.onDestroy();
webView.stopLoading();
webView.clearHistory();
webView.clearFormData();
webView.clearCache(true);
webView.clearView();
webView.destroy();
}
@Override
public void finish() {
super.finish();
AppManager.getAppManager().removeActivity(this);
}
/**
* 设置刷新fragme
*/
private void setRefushFragme() {
mPtrFrame.setPtrHandler(new PtrHandler() {
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame,
View content, View header) {
return PtrDefaultHandler.checkContentCanBePulledDown(frame, webView, header);
}
@Override
public void onRefreshBegin(PtrFrameLayout frame) {
if (!StringUtlis.isEmpty(url)) {
webView.loadUrl(url);
} else {
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
url = bundle.getString("URL");
webView.loadUrl(url);
}
}
}
});
mPtrFrame.setResistance(1.7f);
mPtrFrame.setRatioOfHeaderHeightToRefresh(1.2f);
mPtrFrame.setDurationToClose(200);
mPtrFrame.setPullToRefresh(false);
mPtrFrame.setKeepHeaderWhenRefresh(true);
}
@Override
public void onBackPressed() {
if (fragmentCount() == 0) {
finish();
} else {
super.onBackPressed();
}
}
}
上面代码请提取自己有用的代码,其实就是加载url就进度条显示,加载完成进度条消失
二,框架具体实现
1.PtrClassicDefaultHeader
package com.reach.doooly.pullresh;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.reach.doooly.R;
import com.reach.doooly.base.log.Logs;
public class PtrClassicDefaultHeader extends FrameLayout implements
PtrUIHandler {
private String TAG="PtrClassicDefaultHeader";
private final static String KEY_SharedPreferences = "cube_ptr_classic_last_update";
private static SimpleDateFormat sDataFormat = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
private int mRotateAniTime = 150;
private RotateAnimation mFlipAnimation;
private RotateAnimation mReverseFlipAnimation;
private RelativeLayout header_layout;
private TextView mTitleTextView;
private View mRotateView;
private View mProgressBar;
private long mLastUpdateTime = -1;
private TextView mLastUpdateTextView;
private String mLastUpdateTimeKey;
private boolean mShouldShowLastUpdate;
private LastUpdateTimeUpdater mLastUpdateTimeUpdater = new LastUpdateTimeUpdater();
public PtrClassicDefaultHeader(Context context) {
super(context);
initViews(null);
}
public PtrClassicDefaultHeader(Context context, AttributeSet attrs) {
super(context, attrs);
initViews(attrs);
}
public PtrClassicDefaultHeader(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initViews(attrs);
}
protected void initViews(AttributeSet attrs) {
TypedArray arr = getContext().obtainStyledAttributes(attrs,
R.styleable.PtrClassicHeader, 0, 0);
if (arr != null) {
mRotateAniTime = arr.getInt(
R.styleable.PtrClassicHeader_ptr_rotate_ani_time,
mRotateAniTime);
}
buildAnimation();
View header = LayoutInflater.from(getContext()).inflate(
R.layout.cube_ptr_classic_default_header, this);
mRotateView = header.findViewById(R.id.ptr_classic_header_rotate_view);
mTitleTextView = (TextView) header
.findViewById(R.id.ptr_classic_header_rotate_view_header_title);
mLastUpdateTextView = (TextView) header
.findViewById(R.id.ptr_classic_header_rotate_view_header_last_update);
mProgressBar = header
.findViewById(R.id.ptr_classic_header_rotate_view_progressbar);
header_layout=(RelativeLayout)header.findViewById(R.id.header_layout);
resetView();
}
@Override
protected void onDetachedFromWindow() {
Logs.e(TAG,"onDetachedFromWindow().........");
super.onDetachedFromWindow();
if (mLastUpdateTimeUpdater != null) {
mLastUpdateTimeUpdater.stop();
}
}
public void setRotateAniTime(int time) {
if (time == mRotateAniTime || time == 0) {
return;
}
mRotateAniTime = time;
buildAnimation();
}
/**
* Specify the last update time by this key string
*
* @param key
*/
public void setLastUpdateTimeKey(String key) {
if (TextUtils.isEmpty(key)) {
return;
}
mLastUpdateTimeKey = key;
}
/**
* Using an object to specify the last update time.
*
* @param object
*/
public void setLastUpdateTimeRelateObject(Object object) {
setLastUpdateTimeKey(object.getClass().getName());
}
private void buildAnimation() {
mFlipAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mFlipAnimation.setInterpolator(new LinearInterpolator());
mFlipAnimation.setDuration(mRotateAniTime);
mFlipAnimation.setFillAfter(true);
mReverseFlipAnimation = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
mReverseFlipAnimation.setDuration(mRotateAniTime);
mReverseFlipAnimation.setFillAfter(true);
}
private void resetView() {
hideRotateView();
mProgressBar.setVisibility(INVISIBLE);
}
private void hideRotateView() {
mRotateView.clearAnimation();
mRotateView.setVisibility(INVISIBLE);
}
@Override
public void onUIReset(PtrFrameLayout frame) {
resetView();
mShouldShowLastUpdate = true;
tryUpdateLastUpdateTime();
}
@Override
public void onUIRefreshPrepare(PtrFrameLayout frame) {
header_layout.setVisibility(VISIBLE);
mShouldShowLastUpdate = true;
tryUpdateLastUpdateTime();
mLastUpdateTimeUpdater.start();
mProgressBar.setVisibility(INVISIBLE);
mRotateView.setVisibility(VISIBLE);
mTitleTextView.setVisibility(VISIBLE);
if (frame.isPullToRefresh()) {
mTitleTextView.setText(getResources().getString(
R.string.cube_ptr_pull_down_to_refresh));
} else {
mTitleTextView.setText(getResources().getString(
R.string.cube_ptr_pull_down));
}
}
@Override
public void onUIRefreshBegin(PtrFrameLayout frame) {// 开始加载...
mShouldShowLastUpdate = false;
header_layout.setVisibility(GONE);
hideRotateView();
mProgressBar.setVisibility(VISIBLE);
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(R.string.cube_ptr_refreshing);
tryUpdateLastUpdateTime();
mLastUpdateTimeUpdater.stop();
}
@Override
public void onUIRefreshComplete(PtrFrameLayout frame) {
hideRotateView();
mProgressBar.setVisibility(INVISIBLE);
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(getResources().getString(
R.string.cube_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();
}
}
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);
}
}
}
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.cube_ptr_last_update));
if (seconds < 60) {
sb.append(seconds
+ getContext().getString(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.cube_ptr_hours_ago));
}
} else {
sb.append(minutes
+ getContext().getString(R.string.cube_ptr_minutes_ago));
}
}
return sb.toString();
}
@Override
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch,
byte status, PtrIndicator ptrIndicator) {
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);
if (mRotateView != null) {
mRotateView.clearAnimation();
mRotateView.startAnimation(mReverseFlipAnimation);
}
}
} else if (currentPos > mOffsetToRefresh && lastPos <= mOffsetToRefresh) {
if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
crossRotateLineFromTopUnderTouch(frame);
if (mRotateView != null) {
mRotateView.clearAnimation();
mRotateView.startAnimation(mFlipAnimation);
}
}
}
}
private void crossRotateLineFromTopUnderTouch(PtrFrameLayout frame) {
if (!frame.isPullToRefresh()) {
mTitleTextView.setVisibility(VISIBLE);
mTitleTextView.setText(R.string.cube_ptr_release_to_refresh);
}else{
}
}
private void crossRotateLineFromBottomUnderTouch(PtrFrameLayout frame) {//获取焦点
mTitleTextView.setVisibility(VISIBLE);
if (frame.isPullToRefresh()) {
mTitleTextView.setText(getResources().getString(
R.string.cube_ptr_pull_down_to_refresh));
} else {
mTitleTextView.setText(getResources().getString(
R.string.cube_ptr_pull_down));
}
}
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);
}
}
}
}
2.PtrClassicFrameLayout
package com.reach.doooly.pullresh;
import android.content.Context;
import android.util.AttributeSet;
public class PtrClassicFrameLayout extends PtrFrameLayout {
private PtrClassicDefaultHeader mPtrClassicHeader;
public PtrClassicFrameLayout(Context context) {
super(context);
initViews();
}
public PtrClassicFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initViews();
}
public PtrClassicFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initViews();
}
private void initViews() {
mPtrClassicHeader = new PtrClassicDefaultHeader(getContext());
setHeaderView(mPtrClassicHeader);
addPtrUIHandler(mPtrClassicHeader);
}
public PtrClassicDefaultHeader getHeader() {
return mPtrClassicHeader;
}
/**
* Specify the last update time by this key string
*
* @param key
*/
public void setLastUpdateTimeKey(String key) {
if (mPtrClassicHeader != null) {
mPtrClassicHeader.setLastUpdateTimeKey(key);
}
}
/**
* Using an object to specify the last update time.
*
* @param object
*/
public void setLastUpdateTimeRelateObject(Object object) {
if (mPtrClassicHeader != null) {
mPtrClassicHeader.setLastUpdateTimeRelateObject(object);
}
}
}
3.PtrCLog[可有可无]
package com.reach.doooly.pullresh;
import android.util.Log;
/**
* An encapsulation of {@link Log}, enable log level and print log with parameters.
*
*/
public class PtrCLog {
public static final int LEVEL_VERBOSE = 0;
public static final int LEVEL_DEBUG = 1;
public static final int LEVEL_INFO = 2;
public static final int LEVEL_WARNING = 3;
public static final int LEVEL_ERROR = 4;
public static final int LEVEL_FATAL = 5;
private static int sLevel = LEVEL_VERBOSE;
/**
* set log level, the level lower than this level will not be logged
*
* @param level
*/
public static void setLogLevel(int level) {
sLevel = level;
}
/**
* Send a VERBOSE log message.
*
* @param tag
* @param msg
*/
public static void v(String tag, String msg) {
if (sLevel > LEVEL_VERBOSE) {
return;
}
Log.v(tag, msg);
}
/**
* Send a VERBOSE log message.
*
* @param tag
* @param msg
* @param throwable
*/
public static void v(String tag, String msg, Throwable throwable) {
if (sLevel > LEVEL_VERBOSE) {
return;
}
Log.v(tag, msg, throwable);
}
/**
* Send a VERBOSE log message.
*
* @param tag
* @param msg
* @param args
*/
public static void v(String tag, String msg, Object... args) {
if (sLevel > LEVEL_VERBOSE) {
return;
}
if (args.length > 0) {
msg = String.format(msg, args);
}
Log.v(tag, msg);
}
/**
* Send a DEBUG log message
*
* @param tag
* @param msg
*/
public static void d(String tag, String msg) {
if (sLevel > LEVEL_DEBUG) {
return;
}
Log.d(tag, msg);
}
/**
* Send a DEBUG log message
*
* @param tag
* @param msg
* @param args
*/
public static void d(String tag, String msg, Object... args) {
if (sLevel > LEVEL_DEBUG) {
return;
}
if (args.length > 0) {
msg = String.format(msg, args);
}
Log.d(tag, msg);
}
/**
* Send a DEBUG log message
*
* @param tag
* @param msg
* @param throwable
*/
public static void d(String tag, String msg, Throwable throwable) {
if (sLevel > LEVEL_DEBUG) {
return;
}
Log.d(tag, msg, throwable);
}
/**
* Send an INFO log message
*
* @param tag
* @param msg
*/
public static void i(String tag, String msg) {
if (sLevel > LEVEL_INFO) {
return;
}
Log.i(tag, msg);
}
/**
* Send an INFO log message
*
* @param tag
* @param msg
* @param args
*/
public static void i(String tag, String msg, Object... args) {
if (sLevel > LEVEL_INFO) {
return;
}
if (args.length > 0) {
msg = String.format(msg, args);
}
Log.i(tag, msg);
}
/**
* Send an INFO log message
*
* @param tag
* @param msg
*/
public static void i(String tag, String msg, Throwable throwable) {
if (sLevel > LEVEL_INFO) {
return;
}
Log.i(tag, msg, throwable);
}
/**
* Send a WARNING log message
*
* @param tag
* @param msg
*/
public static void w(String tag, String msg) {
if (sLevel > LEVEL_WARNING) {
return;
}
Log.w(tag, msg);
}
/**
* Send a WARNING log message
*
* @param tag
* @param msg
* @param args
*/
public static void w(String tag, String msg, Object... args) {
if (sLevel > LEVEL_WARNING) {
return;
}
if (args.length > 0) {
msg = String.format(msg, args);
}
Log.w(tag, msg);
}
/**
* Send a WARNING log message
*
* @param tag
* @param msg
* @param throwable
*/
public static void w(String tag, String msg, Throwable throwable) {
if (sLevel > LEVEL_WARNING) {
return;
}
Log.w(tag, msg, throwable);
}
/**
* Send an ERROR log message
*
* @param tag
* @param msg
*/
public static void e(String tag, String msg) {
if (sLevel > LEVEL_ERROR) {
return;
}
Log.e(tag, msg);
}
/**
* Send an ERROR log message
*
* @param tag
* @param msg
* @param args
*/
public static void e(String tag, String msg, Object... args) {
if (sLevel > LEVEL_ERROR) {
return;
}
if (args.length > 0) {
msg = String.format(msg, args);
}
Log.e(tag, msg);
}
/**
* Send an ERROR log message
*
* @param tag
* @param msg
* @param throwable
*/
public static void e(String tag, String msg, Throwable throwable) {
if (sLevel > LEVEL_ERROR) {
return;
}
Log.e(tag, msg, throwable);
}
/**
* Send a FATAL ERROR log message
*
* @param tag
* @param msg
*/
public static void f(String tag, String msg) {
if (sLevel > LEVEL_FATAL) {
return;
}
Log.wtf(tag, msg);
}
/**
* Send a FATAL ERROR log message
*
* @param tag
* @param msg
* @param args
*/
public static void f(String tag, String msg, Object... args) {
if (sLevel > LEVEL_FATAL) {
return;
}
if (args.length > 0) {
msg = String.format(msg, args);
}
Log.wtf(tag, msg);
}
/**
* Send a FATAL ERROR log message
*
* @param tag
* @param msg
* @param throwable
*/
public static void f(String tag, String msg, Throwable throwable) {
if (sLevel > LEVEL_FATAL) {
return;
}
Log.wtf(tag, msg, throwable);
}
}
4.PtrDefaultHandler
package com.reach.doooly.pullresh;
import android.view.View;
import android.widget.AbsListView;
public abstract class PtrDefaultHandler implements PtrHandler {
public static boolean canChildScrollUp(View view) {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (view instanceof AbsListView) {
final AbsListView absListView = (AbsListView) view;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return view.getScrollY() > 0;
}
} else {
return view.canScrollVertically(-1);
}
}
/**
* Default implement for check can perform pull to refresh
*
* @param frame
* @param content
* @param header
* @return
*/
public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) {
return !canChildScrollUp(content);
}
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) {
return checkContentCanBePulledDown(frame, content, header);
}
}
5.PtrFrameLayout
package com.reach.doooly.pullresh;
import com.reach.doooly.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.*;
import android.widget.Scroller;
import android.widget.TextView;
/**
* This layout view for "Pull to Refresh(Ptr)" support all of the view, you can contain everything you want.
* support: pull to refresh / release to refresh / auto refresh / keep header view while refreshing / hide header view while refreshing
*
*/
public class PtrFrameLayout extends ViewGroup {
// status enum
public final static byte PTR_STATUS_INIT = 1;
private byte mStatus = PTR_STATUS_INIT;
public final static byte PTR_STATUS_PREPARE = 2;
public final static byte PTR_STATUS_LOADING = 3;
public final static byte PTR_STATUS_COMPLETE = 4;
private static final boolean DEBUG_LAYOUT = true;
public static boolean DEBUG = false;
private static int ID = 1;
protected final String LOG_TAG = "ptr-frame-" + ++ID;
// auto refresh status
private final static byte FLAG_AUTO_REFRESH_AT_ONCE = 0x01;
private final static byte FLAG_AUTO_REFRESH_BUT_LATER = 0x01 << 1;
private final static byte FLAG_ENABLE_NEXT_PTR_AT_ONCE = 0x01 << 2;
private final static byte FLAG_PIN_CONTENT = 0x01 << 3;
private final static byte MASK_AUTO_REFRESH = 0x03;
protected View mContent;
// optional config for define header and content in xml file
private int mHeaderId = 0;
private int mContainerId = 0;
// config
private int mDurationToClose = 200;
private int mDurationToCloseHeader = 1000;
private boolean mKeepHeaderWhenRefresh = true;
private boolean mPullToRefresh = false;
private View mHeaderView;
private PtrUIHandlerHolder mPtrUIHandlerHolder = PtrUIHandlerHolder.create();
private PtrHandler mPtrHandler;
// working parameters
private ScrollChecker mScrollChecker;
private int mPagingTouchSlop;
private int mHeaderHeight;
private boolean mDisableWhenHorizontalMove = false;
private int mFlag = 0x00;
// disable when detect moving horizontally
private boolean mPreventForHorizontal = false;
private MotionEvent mLastMoveEvent;
private PtrUIHandlerHook mRefreshCompleteHook;
private int mLoadingMinTime = 500;
private long mLoadingStartTime = 0;
private PtrIndicator mPtrIndicator;
private boolean mHasSendCancelEvent = false;
private Runnable mPerformRefreshCompleteDelay = new Runnable() {
@Override
public void run() {
performRefreshComplete();
}
};
public PtrFrameLayout(Context context) {
this(context, null);
}
public PtrFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PtrFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mPtrIndicator = new PtrIndicator();
TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.PtrFrameLayout, 0, 0);
if (arr != null) {
mHeaderId = arr.getResourceId(R.styleable.PtrFrameLayout_ptr_header, mHeaderId);
mContainerId = arr.getResourceId(R.styleable.PtrFrameLayout_ptr_content, mContainerId);
mPtrIndicator.setResistance(
arr.getFloat(R.styleable.PtrFrameLayout_ptr_resistance, mPtrIndicator.getResistance()));
mDurationToClose = arr.getInt(R.styleable.PtrFrameLayout_ptr_duration_to_close, mDurationToClose);
mDurationToCloseHeader = arr.getInt(R.styleable.PtrFrameLayout_ptr_duration_to_close_header, mDurationToCloseHeader);
float ratio = mPtrIndicator.getRatioOfHeaderToHeightRefresh();
ratio = arr.getFloat(R.styleable.PtrFrameLayout_ptr_ratio_of_header_height_to_refresh, ratio);
mPtrIndicator.setRatioOfHeaderHeightToRefresh(ratio);
mKeepHeaderWhenRefresh = arr.getBoolean(R.styleable.PtrFrameLayout_ptr_keep_header_when_refresh, mKeepHeaderWhenRefresh);
mPullToRefresh = arr.getBoolean(R.styleable.PtrFrameLayout_ptr_pull_to_fresh, mPullToRefresh);
arr.recycle();
}
mScrollChecker = new ScrollChecker();
final ViewConfiguration conf = ViewConfiguration.get(getContext());
mPagingTouchSlop = conf.getScaledTouchSlop() * 2;
}
@Override
protected void onFinishInflate() {
final int childCount = getChildCount();
if (childCount > 2) {
throw new IllegalStateException("PtrFrameLayout can only contains 2 children");
} else if (childCount == 2) {
if (mHeaderId != 0 && mHeaderView == null) {
mHeaderView = findViewById(mHeaderId);
}
if (mContainerId != 0 && mContent == null) {
mContent = findViewById(mContainerId);
}
// not specify header or content
if (mContent == null || mHeaderView == null) {
View child1 = getChildAt(0);
View child2 = getChildAt(1);
if (child1 instanceof PtrUIHandler) {
mHeaderView = child1;
mContent = child2;
} else if (child2 instanceof PtrUIHandler) {
mHeaderView = child2;
mContent = child1;
} else {
// both are not specified
if (mContent == null && mHeaderView == null) {
mHeaderView = child1;
mContent = child2;
}
// only one is specified
else {
if (mHeaderView == null) {
mHeaderView = mContent == child1 ? child2 : child1;
} else {
mContent = mHeaderView == child1 ? child2 : child1;
}
}
}
}
} else if (childCount == 1) {
mContent = getChildAt(0);
} else {
TextView errorView = new TextView(getContext());
errorView.setClickable(true);
errorView.setTextColor(0xffff6600);
errorView.setGravity(Gravity.CENTER);
errorView.setTextSize(20);
errorView.setText("The content view in PtrFrameLayout is empty. Do you forget to specify its id in xml layout file?");
mContent = errorView;
addView(mContent);
}
if (mHeaderView != null) {
mHeaderView.bringToFront();
}
super.onFinishInflate();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mScrollChecker != null) {
mScrollChecker.destroy();
}
if (mPerformRefreshCompleteDelay != null) {
removeCallbacks(mPerformRefreshCompleteDelay);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (isDebug()) {
PtrCLog.d(LOG_TAG, "onMeasure frame: width: %s, height: %s, padding: %s %s %s %s",
getMeasuredHeight(), getMeasuredWidth(),
getPaddingLeft(), getPaddingRight(), getPaddingTop(), getPaddingBottom());
}
if (mHeaderView != null) {
measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0);
MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
mPtrIndicator.setHeaderHeight(mHeaderHeight);
}
if (mContent != null) {
measureContentView(mContent, widthMeasureSpec, heightMeasureSpec);
if (isDebug()) {
MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams();
PtrCLog.d(LOG_TAG, "onMeasure content, width: %s, height: %s, margin: %s %s %s %s",
getMeasuredWidth(), getMeasuredHeight(),
lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin);
PtrCLog.d(LOG_TAG, "onMeasure, currentPos: %s, lastPos: %s, top: %s",
mPtrIndicator.getCurrentPosY(), mPtrIndicator.getLastPosY(), mContent.getTop());
}
}
}
private void measureContentView(View child,
int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
getPaddingTop() + getPaddingBottom() + lp.topMargin, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@Override
protected void onLayout(boolean flag, int i, int j, int k, int l) {
layoutChildren();
}
private void layoutChildren() {
int offset = mPtrIndicator.getCurrentPosY();
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
if (mHeaderView != null) {
MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
final int left = paddingLeft + lp.leftMargin;
// enhance readability(header is layout above screen when first init)
final int top = -(mHeaderHeight - paddingTop - lp.topMargin - offset);
final int right = left + mHeaderView.getMeasuredWidth();
final int bottom = top + mHeaderView.getMeasuredHeight();
mHeaderView.layout(left, top, right, bottom);
if (isDebug()) {
PtrCLog.d(LOG_TAG, "onLayout header: %s %s %s %s", left, top, right, bottom);
}
}
if (mContent != null) {
if (isPinContent()) {
offset = 0;
}
MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams();
final int left = paddingLeft + lp.leftMargin;
final int top = paddingTop + lp.topMargin + offset;
final int right = left + mContent.getMeasuredWidth();
final int bottom = top + mContent.getMeasuredHeight();
if (isDebug()) {
PtrCLog.d(LOG_TAG, "onLayout content: %s %s %s %s", left, top, right, bottom);
}
mContent.layout(left, top, right, bottom);
}
}
@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
private boolean isDebug() {
return DEBUG && DEBUG_LAYOUT;
}
public boolean dispatchTouchEventSupper(MotionEvent e) {
return super.dispatchTouchEvent(e);
}
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
if (!isEnabled() || mContent == null || mHeaderView == null) {
return dispatchTouchEventSupper(e);
}
int action = e.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mPtrIndicator.onRelease();
if (mPtrIndicator.hasLeftStartPosition()) {
if (DEBUG) {
PtrCLog.d(LOG_TAG, "call onRelease when user release");
}
onRelease(false);
if (mPtrIndicator.hasMovedAfterPressedDown()) {
sendCancelEvent();
return true;
}
return dispatchTouchEventSupper(e);
} else {
return dispatchTouchEventSupper(e);
}
case MotionEvent.ACTION_DOWN:
mHasSendCancelEvent = false;
mPtrIndicator.onPressDown(e.getX(), e.getY());
mScrollChecker.abortIfWorking();
mPreventForHorizontal = false;
// The cancel event will be sent once the position is moved.
// So let the event pass to children.
// fix #93, #102
dispatchTouchEventSupper(e);
return true;
case MotionEvent.ACTION_MOVE:
mLastMoveEvent = e;
mPtrIndicator.onMove(e.getX(), e.getY());
float offsetX = mPtrIndicator.getOffsetX();
float offsetY = mPtrIndicator.getOffsetY();
if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
if (mPtrIndicator.isInStartPosition()) {
mPreventForHorizontal = true;
}
}
if (mPreventForHorizontal) {
return dispatchTouchEventSupper(e);
}
boolean moveDown = offsetY > 0;
boolean moveUp = !moveDown;
boolean canMoveUp = mPtrIndicator.hasLeftStartPosition();
if (DEBUG) {
boolean canMoveDown = mPtrHandler != null && mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView);
PtrCLog.v(LOG_TAG, "ACTION_MOVE: offsetY:%s, currentPos: %s, moveUp: %s, canMoveUp: %s, moveDown: %s: canMoveDown: %s", offsetY, mPtrIndicator.getCurrentPosY(), moveUp, canMoveUp, moveDown, canMoveDown);
}
// disable move when header not reach top
if (moveDown && mPtrHandler != null && !mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView)) {
return dispatchTouchEventSupper(e);
}
if ((moveUp && canMoveUp) || moveDown) {
movePos(offsetY);
return true;
}
}
return dispatchTouchEventSupper(e);
}
/**
* if deltaY > 0, move the content down
*
* @param deltaY
*/
private void movePos(float deltaY) {
// has reached the top
if ((deltaY < 0 && mPtrIndicator.isInStartPosition())) {
if (DEBUG) {
PtrCLog.e(LOG_TAG, String.format("has reached the top"));
}
return;
}
int to = mPtrIndicator.getCurrentPosY() + (int) deltaY;
// over top
if (mPtrIndicator.willOverTop(to)) {
if (DEBUG) {
PtrCLog.e(LOG_TAG, String.format("over top"));
}
to = PtrIndicator.POS_START;
}
mPtrIndicator.setCurrentPos(to);
int change = to - mPtrIndicator.getLastPosY();
updatePos(change);
}
private void updatePos(int change) {
if (change == 0) {
return;
}
boolean isUnderTouch = mPtrIndicator.isUnderTouch();
// once moved, cancel event will be sent to child
if (isUnderTouch && !mHasSendCancelEvent && mPtrIndicator.hasMovedAfterPressedDown()) {
mHasSendCancelEvent = true;
sendCancelEvent();
}
// leave initiated position or just refresh complete
if ((mPtrIndicator.hasJustLeftStartPosition() && mStatus == PTR_STATUS_INIT) ||
(mPtrIndicator.goDownCrossFinishPosition() && mStatus == PTR_STATUS_COMPLETE && isEnabledNextPtrAtOnce())) {
mStatus = PTR_STATUS_PREPARE;
mPtrUIHandlerHolder.onUIRefreshPrepare(this);
if (DEBUG) {
PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag);
}
}
// back to initiated position
if (mPtrIndicator.hasJustBackToStartPosition()) {
tryToNotifyReset();
// recover event to children
if (isUnderTouch) {
sendDownEvent();
}
}
// Pull to Refresh
if (mStatus == PTR_STATUS_PREPARE) {
// reach fresh height while moving from top to bottom
if (isUnderTouch && !isAutoRefresh() && mPullToRefresh
&& mPtrIndicator.crossRefreshLineFromTopToBottom()) {
tryToPerformRefresh();
}
// reach header height while auto refresh
if (performAutoRefreshButLater() && mPtrIndicator.hasJustReachedHeaderHeightFromTopToBottom()) {
tryToPerformRefresh();
}
}
if (DEBUG) {
PtrCLog.v(LOG_TAG, "updatePos: change: %s, current: %s last: %s, top: %s, headerHeight: %s",
change, mPtrIndicator.getCurrentPosY(), mPtrIndicator.getLastPosY(), mContent.getTop(), mHeaderHeight);
}
mHeaderView.offsetTopAndBottom(change);
if (!isPinContent()) {
mContent.offsetTopAndBottom(change);
}
invalidate();
if (mPtrUIHandlerHolder.hasHandler()) {
mPtrUIHandlerHolder.onUIPositionChange(this, isUnderTouch, mStatus, mPtrIndicator);
}
onPositionChange(isUnderTouch, mStatus, mPtrIndicator);
}
protected void onPositionChange(boolean isInTouching, byte status, PtrIndicator mPtrIndicator) {
}
@SuppressWarnings("unused")
public int getHeaderHeight() {
return mHeaderHeight;
}
private void onRelease(boolean stayForLoading) {
tryToPerformRefresh();
if (mStatus == PTR_STATUS_LOADING) {
// keep header for fresh
if (mKeepHeaderWhenRefresh) {
// scroll header back
if (mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && !stayForLoading) {
mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToKeepHeaderWhileLoading(), mDurationToClose);
} else {
// do nothing
}
} else {
tryScrollBackToTopWhileLoading();
}
} else {
if (mStatus == PTR_STATUS_COMPLETE) {
notifyUIRefreshComplete(false);
} else {
tryScrollBackToTopAbortRefresh();
}
}
}
/**
* please DO REMEMBER resume the hook
*
* @param hook
*/
public void setRefreshCompleteHook(PtrUIHandlerHook hook) {
mRefreshCompleteHook = hook;
hook.setResumeAction(new Runnable() {
@Override
public void run() {
if (DEBUG) {
PtrCLog.d(LOG_TAG, "mRefreshCompleteHook resume.");
}
notifyUIRefreshComplete(true);
}
});
}
/**
* Scroll back to to if is not under touch
*/
private void tryScrollBackToTop() {
if (!mPtrIndicator.isUnderTouch()) {
mScrollChecker.tryToScrollTo(PtrIndicator.POS_START, mDurationToCloseHeader);
}
}
/**
* just make easier to understand
*/
private void tryScrollBackToTopWhileLoading() {
tryScrollBackToTop();
}
/**
* just make easier to understand
*/
private void tryScrollBackToTopAfterComplete() {
tryScrollBackToTop();
}
/**
* just make easier to understand
*/
private void tryScrollBackToTopAbortRefresh() {
tryScrollBackToTop();
}
private boolean tryToPerformRefresh() {
if (mStatus != PTR_STATUS_PREPARE) {
return false;
}
//
if ((mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && isAutoRefresh()) || mPtrIndicator.isOverOffsetToRefresh()) {
mStatus = PTR_STATUS_LOADING;
performRefresh();
}
return false;
}
private void performRefresh() {
mLoadingStartTime = System.currentTimeMillis();
if (mPtrUIHandlerHolder.hasHandler()) {
mPtrUIHandlerHolder.onUIRefreshBegin(this);
if (DEBUG) {
PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshBegin");
}
}
if (mPtrHandler != null) {
//下面为后期自己add的
tryScrollBackToTop();
if (mPtrUIHandlerHolder.hasHandler()) {
mPtrUIHandlerHolder.onUIReset(this);
if (DEBUG) {
PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIReset");
}
}
mStatus = PTR_STATUS_INIT;
clearFlag();
//自己addd框架的代码结束
mPtrHandler.onRefreshBegin(this);
}
}
/**
* If at the top and not in loading, reset
*/
private boolean tryToNotifyReset() {
if ((mStatus == PTR_STATUS_COMPLETE || mStatus == PTR_STATUS_PREPARE) && mPtrIndicator.isInStartPosition()) {
if (mPtrUIHandlerHolder.hasHandler()) {
mPtrUIHandlerHolder.onUIReset(this);
if (DEBUG) {
PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIReset");
}
}
mStatus = PTR_STATUS_INIT;
clearFlag();
return true;
}
return false;
}
protected void onPtrScrollAbort() {
if (mPtrIndicator.hasLeftStartPosition() && isAutoRefresh()) {
if (DEBUG) {
PtrCLog.d(LOG_TAG, "call onRelease after scroll abort");
}
onRelease(true);
}
}
protected void onPtrScrollFinish() {
if (mPtrIndicator.hasLeftStartPosition() && isAutoRefresh()) {
if (DEBUG) {
PtrCLog.d(LOG_TAG, "call onRelease after scroll finish");
}
onRelease(true);
}
}
/**
* Detect whether is refreshing.
*
* @return
*/
public boolean isRefreshing() {
return mStatus == PTR_STATUS_LOADING;
}
/**
* Call this when data is loaded.
* The UI will perform complete at once or after a delay, depends on the time elapsed is greater then {@link #mLoadingMinTime} or not.
*/
final public void refreshComplete() {
if (DEBUG) {
PtrCLog.i(LOG_TAG, "refreshComplete");
}
if (mRefreshCompleteHook != null) {
mRefreshCompleteHook.reset();
}
int delay = (int) (mLoadingMinTime - (System.currentTimeMillis() - mLoadingStartTime));
if (delay <= 0) {
if (DEBUG) {
PtrCLog.d(LOG_TAG, "performRefreshComplete at once");
}
performRefreshComplete();
} else {
postDelayed(mPerformRefreshCompleteDelay, delay);
if (DEBUG) {
PtrCLog.d(LOG_TAG, "performRefreshComplete after delay: %s", delay);
}
}
}
/**
* Do refresh complete work when time elapsed is greater than {@link #mLoadingMinTime}
*/
private void performRefreshComplete() {
mStatus = PTR_STATUS_COMPLETE;
// if is auto refresh do nothing, wait scroller stop
if (mScrollChecker.mIsRunning && isAutoRefresh()) {
// do nothing
if (DEBUG) {
PtrCLog.d(LOG_TAG, "performRefreshComplete do nothing, scrolling: %s, auto refresh: %s",
mScrollChecker.mIsRunning, mFlag);
}
return;
}
notifyUIRefreshComplete(false);
}
/**
* Do real refresh work. If there is a hook, execute the hook first.
*
* @param ignoreHook
*/
private void notifyUIRefreshComplete(boolean ignoreHook) {
/**
* After hook operation is done, {@link #notifyUIRefreshComplete} will be call in resume action to ignore hook.
*/
if (mPtrIndicator.hasLeftStartPosition() && !ignoreHook && mRefreshCompleteHook != null) {
if (DEBUG) {
PtrCLog.d(LOG_TAG, "notifyUIRefreshComplete mRefreshCompleteHook run.");
}
mRefreshCompleteHook.takeOver();
return;
}
if (mPtrUIHandlerHolder.hasHandler()) {
if (DEBUG) {
PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshComplete");
}
mPtrUIHandlerHolder.onUIRefreshComplete(this);
}
mPtrIndicator.onUIRefreshComplete();
tryScrollBackToTopAfterComplete();
tryToNotifyReset();
}
public void autoRefresh() {
autoRefresh(true, mDurationToCloseHeader);
}
public void autoRefresh(boolean atOnce) {
autoRefresh(atOnce, mDurationToCloseHeader);
}
private void clearFlag() {
// remove auto fresh flag
mFlag = mFlag & ~MASK_AUTO_REFRESH;
}
public void autoRefresh(boolean atOnce, int duration) {
if (mStatus != PTR_STATUS_INIT) {
return;
}
mFlag |= atOnce ? FLAG_AUTO_REFRESH_AT_ONCE : FLAG_AUTO_REFRESH_BUT_LATER;
mStatus = PTR_STATUS_PREPARE;
if (mPtrUIHandlerHolder.hasHandler()) {
mPtrUIHandlerHolder.onUIRefreshPrepare(this);
if (DEBUG) {
PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag);
}
}
mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToRefresh(), duration);
if (atOnce) {
mStatus = PTR_STATUS_LOADING;
performRefresh();
}
}
public boolean isAutoRefresh() {
return (mFlag & MASK_AUTO_REFRESH) > 0;
}
private boolean performAutoRefreshButLater() {
return (mFlag & MASK_AUTO_REFRESH) == FLAG_AUTO_REFRESH_BUT_LATER;
}
public boolean isEnabledNextPtrAtOnce() {
return (mFlag & FLAG_ENABLE_NEXT_PTR_AT_ONCE) > 0;
}
/**
* If @param enable has been set to true. The user can perform next PTR at once.
*
* @param enable
*/
public void setEnabledNextPtrAtOnce(boolean enable) {
if (enable) {
mFlag = mFlag | FLAG_ENABLE_NEXT_PTR_AT_ONCE;
} else {
mFlag = mFlag & ~FLAG_ENABLE_NEXT_PTR_AT_ONCE;
}
}
public boolean isPinContent() {
return (mFlag & FLAG_PIN_CONTENT) > 0;
}
/**
* The content view will now move when {@param pinContent} set to true.
*
* @param pinContent
*/
public void setPinContent(boolean pinContent) {
if (pinContent) {
mFlag = mFlag | FLAG_PIN_CONTENT;
} else {
mFlag = mFlag & ~FLAG_PIN_CONTENT;
}
}
/**
* It's useful when working with viewpager.
*
* @param disable
*/
public void disableWhenHorizontalMove(boolean disable) {
mDisableWhenHorizontalMove = disable;
}
/**
* loading will last at least for so long
*
* @param time
*/
public void setLoadingMinTime(int time) {
mLoadingMinTime = time;
}
/**
* Not necessary any longer. Once moved, cancel event will be sent to child.
*
* @param yes
*/
@Deprecated
public void setInterceptEventWhileWorking(boolean yes) {
}
@SuppressWarnings({"unused"})
public View getContentView() {
return mContent;
}
public void setPtrHandler(PtrHandler ptrHandler) {
mPtrHandler = ptrHandler;
}
public void addPtrUIHandler(PtrUIHandler ptrUIHandler) {
PtrUIHandlerHolder.addHandler(mPtrUIHandlerHolder, ptrUIHandler);
}
@SuppressWarnings({"unused"})
public void removePtrUIHandler(PtrUIHandler ptrUIHandler) {
mPtrUIHandlerHolder = PtrUIHandlerHolder.removeHandler(mPtrUIHandlerHolder, ptrUIHandler);
}
public void setPtrIndicator(PtrIndicator slider) {
if (mPtrIndicator != null && mPtrIndicator != slider) {
slider.convertFrom(mPtrIndicator);
}
mPtrIndicator = slider;
}
@SuppressWarnings({"unused"})
public float getResistance() {
return mPtrIndicator.getResistance();
}
public void setResistance(float resistance) {
mPtrIndicator.setResistance(resistance);
}
@SuppressWarnings({"unused"})
public float getDurationToClose() {
return mDurationToClose;
}
/**
* The duration to return back to the refresh position
*
* @param duration
*/
public void setDurationToClose(int duration) {
mDurationToClose = duration;
}
@SuppressWarnings({"unused"})
public long getDurationToCloseHeader() {
return mDurationToCloseHeader;
}
/**
* The duration to close time
*
* @param duration
*/
public void setDurationToCloseHeader(int duration) {
mDurationToCloseHeader = duration;
}
public void setRatioOfHeaderHeightToRefresh(float ratio) {
mPtrIndicator.setRatioOfHeaderHeightToRefresh(ratio);
}
public int getOffsetToRefresh() {
return mPtrIndicator.getOffsetToRefresh();
}
@SuppressWarnings({"unused"})
public void setOffsetToRefresh(int offset) {
mPtrIndicator.setOffsetToRefresh(offset);
}
@SuppressWarnings({"unused"})
public float getRatioOfHeaderToHeightRefresh() {
return mPtrIndicator.getRatioOfHeaderToHeightRefresh();
}
@SuppressWarnings({"unused"})
public int getOffsetToKeepHeaderWhileLoading() {
return mPtrIndicator.getOffsetToKeepHeaderWhileLoading();
}
@SuppressWarnings({"unused"})
public void setOffsetToKeepHeaderWhileLoading(int offset) {
mPtrIndicator.setOffsetToKeepHeaderWhileLoading(offset);
}
@SuppressWarnings({"unused"})
public boolean isKeepHeaderWhenRefresh() {
return mKeepHeaderWhenRefresh;
}
public void setKeepHeaderWhenRefresh(boolean keepOrNot) {
mKeepHeaderWhenRefresh = keepOrNot;
}
public boolean isPullToRefresh() {
return mPullToRefresh;
}
public void setPullToRefresh(boolean pullToRefresh) {
mPullToRefresh = pullToRefresh;
}
@SuppressWarnings({"unused"})
public View getHeaderView() {
return mHeaderView;
}
public void setHeaderView(View header) {
if (mHeaderView != null && header != null && mHeaderView != header) {
removeView(mHeaderView);
}
ViewGroup.LayoutParams lp = header.getLayoutParams();
if (lp == null) {
lp = new LayoutParams(-1, -2);
header.setLayoutParams(lp);
}
mHeaderView = header;
addView(header);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p != null && p instanceof LayoutParams;
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
private void sendCancelEvent() {
if (DEBUG) {
PtrCLog.d(LOG_TAG, "send cancel event");
}
// The ScrollChecker will update position and lead to send cancel event when mLastMoveEvent is null.
// fix #104, #80, #92
if (mLastMoveEvent == null) {
return;
}
MotionEvent last = mLastMoveEvent;
MotionEvent e = MotionEvent.obtain(last.getDownTime(), last.getEventTime() + ViewConfiguration.getLongPressTimeout(), MotionEvent.ACTION_CANCEL, last.getX(), last.getY(), last.getMetaState());
dispatchTouchEventSupper(e);
}
private void sendDownEvent() {
if (DEBUG) {
PtrCLog.d(LOG_TAG, "send down event");
}
final MotionEvent last = mLastMoveEvent;
MotionEvent e = MotionEvent.obtain(last.getDownTime(), last.getEventTime(), MotionEvent.ACTION_DOWN, last.getX(), last.getY(), last.getMetaState());
dispatchTouchEventSupper(e);
}
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
@SuppressWarnings({"unused"})
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
class ScrollChecker implements Runnable {
private int mLastFlingY;
private Scroller mScroller;
private boolean mIsRunning = false;
private int mStart;
private int mTo;
public ScrollChecker() {
mScroller = new Scroller(getContext());
}
public void run() {
boolean finish = !mScroller.computeScrollOffset() || mScroller.isFinished();
int curY = mScroller.getCurrY();
int deltaY = curY - mLastFlingY;
if (DEBUG) {
if (deltaY != 0) {
PtrCLog.v(LOG_TAG,
"scroll: %s, start: %s, to: %s, currentPos: %s, current :%s, last: %s, delta: %s",
finish, mStart, mTo, mPtrIndicator.getCurrentPosY(), curY, mLastFlingY, deltaY);
}
}
if (!finish) {
mLastFlingY = curY;
movePos(deltaY);
post(this);
} else {
finish();
}
}
private void finish() {
if (DEBUG) {
PtrCLog.v(LOG_TAG, "finish, currentPos:%s", mPtrIndicator.getCurrentPosY());
}
reset();
onPtrScrollFinish();
}
private void reset() {
mIsRunning = false;
mLastFlingY = 0;
removeCallbacks(this);
}
private void destroy() {
reset();
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
}
public void abortIfWorking() {
if (mIsRunning) {
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
onPtrScrollAbort();
reset();
}
}
public void tryToScrollTo(int to, int duration) {
if (mPtrIndicator.isAlreadyHere(to)) {
return;
}
mStart = mPtrIndicator.getCurrentPosY();
mTo = to;
int distance = to - mStart;
if (DEBUG) {
PtrCLog.d(LOG_TAG, "tryToScrollTo: start: %s, distance:%s, to:%s", mStart, distance, to);
}
removeCallbacks(this);
mLastFlingY = 0;
// fix #47: Scroller should be reused, https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh/issues/47
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
mScroller.startScroll(0, 0, 0, distance, duration);
post(this);
mIsRunning = true;
}
}
}
6.PtrHandler
package com.reach.doooly.pullresh;
import android.view.View;
public interface PtrHandler {
/**
* Check can do refresh or not. For example the content is empty or the first child is in view.
* <p/>
* {@link in.srain.cube.views.ptr.PtrDefaultHandler#checkContentCanBePulledDown}
*/
public boolean checkCanDoRefresh(final PtrFrameLayout frame, final View content, final View header);
/**
* When refresh begin
*
* @param frame
*/
public void onRefreshBegin(final PtrFrameLayout frame);
}
7.PtrIndicator
package com.reach.doooly.pullresh;
import android.graphics.PointF;
public class PtrIndicator {
public final static int POS_START = 0;
protected int mOffsetToRefresh = 0;
private PointF mPtLastMove = new PointF();
private float mOffsetX;
private float mOffsetY;
private int mCurrentPos = 0;
private int mLastPos = 0;
private int mHeaderHeight;
private int mPressedPos = 0;
private float mRatioOfHeaderHeightToRefresh = 1.2f;
private float mResistance = 1.7f;
private boolean mIsUnderTouch = false;
private int mOffsetToKeepHeaderWhileLoading = -1;
// record the refresh complete position
private int mRefreshCompleteY = 0;
public boolean isUnderTouch() {
return mIsUnderTouch;
}
public float getResistance() {
return mResistance;
}
public void setResistance(float resistance) {
mResistance = resistance;
}
public void onRelease() {
mIsUnderTouch = false;
}
public void onUIRefreshComplete() {
mRefreshCompleteY = mCurrentPos;
}
public boolean goDownCrossFinishPosition() {
return mCurrentPos >= mRefreshCompleteY;
}
protected void processOnMove(float currentX, float currentY, float offsetX, float offsetY) {
setOffset(offsetX, offsetY / mResistance);
}
public void setRatioOfHeaderHeightToRefresh(float ratio) {
mRatioOfHeaderHeightToRefresh = ratio;
mOffsetToRefresh = (int) (mHeaderHeight * ratio);
}
public float getRatioOfHeaderToHeightRefresh() {
return mRatioOfHeaderHeightToRefresh;
}
public int getOffsetToRefresh() {
return mOffsetToRefresh;
}
public void setOffsetToRefresh(int offset) {
mRatioOfHeaderHeightToRefresh = mHeaderHeight * 1f / offset;
mOffsetToRefresh = offset;
}
public void onPressDown(float x, float y) {
mIsUnderTouch = true;
mPressedPos = mCurrentPos;
mPtLastMove.set(x, y);
}
public final void onMove(float x, float y) {
float offsetX = x - mPtLastMove.x;
float offsetY = (y - mPtLastMove.y);
processOnMove(x, y, offsetX, offsetY);
mPtLastMove.set(x, y);
}
protected void setOffset(float x, float y) {
mOffsetX = x;
mOffsetY = y;
}
public float getOffsetX() {
return mOffsetX;
}
public float getOffsetY() {
return mOffsetY;
}
public int getLastPosY() {
return mLastPos;
}
public int getCurrentPosY() {
return mCurrentPos;
}
/**
* Update current position before update the UI
*/
public final void setCurrentPos(int current) {
mLastPos = mCurrentPos;
mCurrentPos = current;
onUpdatePos(current, mLastPos);
}
protected void onUpdatePos(int current, int last) {
}
public int getHeaderHeight() {
return mHeaderHeight;
}
public void setHeaderHeight(int height) {
mHeaderHeight = height;
updateHeight();
}
protected void updateHeight() {
mOffsetToRefresh = (int) (mRatioOfHeaderHeightToRefresh * mHeaderHeight);
}
public void convertFrom(PtrIndicator ptrSlider) {
mCurrentPos = ptrSlider.mCurrentPos;
mLastPos = ptrSlider.mLastPos;
mHeaderHeight = ptrSlider.mHeaderHeight;
}
public boolean hasLeftStartPosition() {
return mCurrentPos > POS_START;
}
public boolean hasJustLeftStartPosition() {
return mLastPos == POS_START && hasLeftStartPosition();
}
public boolean hasJustBackToStartPosition() {
return mLastPos != POS_START && isInStartPosition();
}
public boolean isOverOffsetToRefresh() {
return mCurrentPos >= getOffsetToRefresh();
}
public boolean hasMovedAfterPressedDown() {
return mCurrentPos != mPressedPos;
}
public boolean isInStartPosition() {
return mCurrentPos == POS_START;
}
public boolean crossRefreshLineFromTopToBottom() {
return mLastPos < getOffsetToRefresh() && mCurrentPos >= getOffsetToRefresh();
}
public boolean hasJustReachedHeaderHeightFromTopToBottom() {
return mLastPos < mHeaderHeight && mCurrentPos >= mHeaderHeight;
}
public boolean isOverOffsetToKeepHeaderWhileLoading() {
return mCurrentPos > getOffsetToKeepHeaderWhileLoading();
}
public void setOffsetToKeepHeaderWhileLoading(int offset) {
mOffsetToKeepHeaderWhileLoading = offset;
}
public int getOffsetToKeepHeaderWhileLoading() {
return mOffsetToKeepHeaderWhileLoading >= 0 ? mOffsetToKeepHeaderWhileLoading : mHeaderHeight;
}
public boolean isAlreadyHere(int to) {
return mCurrentPos == to;
}
public float getLastPercent() {
final float oldPercent = mHeaderHeight == 0 ? 0 : mLastPos * 1f / mHeaderHeight;
return oldPercent;
}
public float getCurrentPercent() {
final float currentPercent = mHeaderHeight == 0 ? 0 : mCurrentPos * 1f / mHeaderHeight;
return currentPercent;
}
public boolean willOverTop(int to) {
return to < POS_START;
}
}
8.PtrUIHandler
package com.reach.doooly.pullresh;
/**
*
*/
public interface PtrUIHandler {
/**
* When the content view has reached top and refresh has been completed, view will be reset.
*
* @param frame
*/
public void onUIReset(PtrFrameLayout frame);
/**
* prepare for loading
*
* @param frame
*/
public void onUIRefreshPrepare(PtrFrameLayout frame);
/**
* perform refreshing UI
*/
public void onUIRefreshBegin(PtrFrameLayout frame);
/**
* perform UI after refresh
*/
public void onUIRefreshComplete(PtrFrameLayout frame);
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator);
}
9.PtrUIHandlerHolder
package com.reach.doooly.pullresh;
/**
* A single linked list to wrap PtrUIHandler
*/
class PtrUIHandlerHolder implements PtrUIHandler {
private PtrUIHandler mHandler;
private PtrUIHandlerHolder mNext;
private boolean contains(PtrUIHandler handler) {
return mHandler != null && mHandler == handler;
}
private PtrUIHandlerHolder() {
}
public boolean hasHandler() {
return mHandler != null;
}
private PtrUIHandler getHandler() {
return mHandler;
}
public static void addHandler(PtrUIHandlerHolder head, PtrUIHandler handler) {
if (null == handler) {
return;
}
if (head == null) {
return;
}
if (null == head.mHandler) {
head.mHandler = handler;
return;
}
PtrUIHandlerHolder current = head;
for (; ; current = current.mNext) {
// duplicated
if (current.contains(handler)) {
return;
}
if (current.mNext == null) {
break;
}
}
PtrUIHandlerHolder newHolder = new PtrUIHandlerHolder();
newHolder.mHandler = handler;
current.mNext = newHolder;
}
public static PtrUIHandlerHolder create() {
return new PtrUIHandlerHolder();
}
public static PtrUIHandlerHolder removeHandler(PtrUIHandlerHolder head, PtrUIHandler handler) {
if (head == null || handler == null || null == head.mHandler) {
return head;
}
PtrUIHandlerHolder current = head;
PtrUIHandlerHolder pre = null;
do {
// delete current: link pre to next, unlink next from current;
// pre will no change, current move to next element;
if (current.contains(handler)) {
// current is head
if (pre == null) {
head = current.mNext;
current.mNext = null;
current = head;
} else {
pre.mNext = current.mNext;
current.mNext = null;
current = pre.mNext;
}
} else {
pre = current;
current = current.mNext;
}
} while (current != null);
if (head == null) {
head = new PtrUIHandlerHolder();
}
return head;
}
@Override
public void onUIReset(PtrFrameLayout frame) {
PtrUIHandlerHolder current = this;
do {
final PtrUIHandler handler = current.getHandler();
if (null != handler) {
handler.onUIReset(frame);
}
} while ((current = current.mNext) != null);
}
@Override
public void onUIRefreshPrepare(PtrFrameLayout frame) {
if (!hasHandler()) {
return;
}
PtrUIHandlerHolder current = this;
do {
final PtrUIHandler handler = current.getHandler();
if (null != handler) {
handler.onUIRefreshPrepare(frame);
}
} while ((current = current.mNext) != null);
}
@Override
public void onUIRefreshBegin(PtrFrameLayout frame) {
PtrUIHandlerHolder current = this;
do {
final PtrUIHandler handler = current.getHandler();
if (null != handler) {
handler.onUIRefreshBegin(frame);
}
} while ((current = current.mNext) != null);
}
@Override
public void onUIRefreshComplete(PtrFrameLayout frame) {
PtrUIHandlerHolder current = this;
do {
final PtrUIHandler handler = current.getHandler();
if (null != handler) {
handler.onUIRefreshComplete(frame);
}
} while ((current = current.mNext) != null);
}
@Override
public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
PtrUIHandlerHolder current = this;
do {
final PtrUIHandler handler = current.getHandler();
if (null != handler) {
handler.onUIPositionChange(frame, isUnderTouch, status, ptrIndicator);
}
} while ((current = current.mNext) != null);
}
}
10.PtrUIHandlerHook
package com.reach.doooly.pullresh;
/**
* Run a hook runnable, the runnable will run only once.
* After the runnable is done, call resume to resume.
* Once run, call takeover will directory call the resume action
*/
public abstract class PtrUIHandlerHook implements Runnable {
private Runnable mResumeAction;
private static final byte STATUS_PREPARE = 0;
private static final byte STATUS_IN_HOOK = 1;
private static final byte STATUS_RESUMED = 2;
private byte mStatus = STATUS_PREPARE;
public void takeOver() {
takeOver(null);
}
public void takeOver(Runnable resumeAction) {
if (resumeAction != null) {
mResumeAction = resumeAction;
}
switch (mStatus) {
case STATUS_PREPARE:
mStatus = STATUS_IN_HOOK;
run();
break;
case STATUS_IN_HOOK:
break;
case STATUS_RESUMED:
resume();
break;
}
}
public void reset() {
mStatus = STATUS_PREPARE;
}
public void resume() {
if (mResumeAction != null) {
mResumeAction.run();
}
mStatus = STATUS_RESUMED;
}
/**
* Hook should always have a resume action, which is hooked by this hook.
*
* @param runnable
*/
public void setResumeAction(Runnable runnable) {
mResumeAction = runnable;
}
}
原理比较简单,和普通的下拉刷新的实现差不多,尤其是距离算法。顾这里原理就不详解了欧。
希望这篇博客对亲有用