效果
一個仿寫QQ的刷新控件,只是樣子不跟QQ的一樣但是基本功能是一致的,同樣具有刷新功能和加載更多的功能。
使用
類文件 | apk |
---|---|
下載 | 下載 |
下載類文件之後有如下文件:
更新
BUG-1 ==》在ScrollView滑動問題:
對下載的BrushRequestLayout.java類中isContentViewLoadEnable()方法進行替換如下:
/**
* 內容區域是否可以加載
*
* @return
*/
private boolean isContentViewLoadEnable() {
if (scrollView != null && scrollView.getChildAt(0).getMeasuredHeight() > scrollView.getScrollY() + scrollView.getHeight()) {
return false;
}
if (absListView != null && absListView.getLastVisiblePosition() < (absListView.getCount() - 1)) {
return false;
}
return true;
}
複製對應的圖片和佈局還有attrs到自己項目,還有主要的BrushRequestLayout.java類,包名修改爲自己項目的包名即可。
(1)xml使用
<com.android.widget.BrushRequestLayout
android:id="@+id/prl"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"></ListView>
</com.android.widget.BrushRequestLayout>
(2)attrs.xml屬性值
<declare-styleable name="BrushRequestLayout">
<attr name="headerHeight" format="dimension" />
<attr name="footerHeight" format="dimension" />
<attr name="refreshEnable" format="boolean" />
<attr name="loadEnable" format="boolean" />
<attr name="duration" format="integer" />
</declare-styleable>
(3)自動刷新
BrushRequestLayout prl = (BrushRequestLayout) findViewById(R.id.prl);
prl.setRefreshing(true);
(4)自動加載
BrushRequestLayout prl = (BrushRequestLayout) findViewById(R.id.prl);
prl.setLoading(true);
(5)刷新監聽
BrushRequestLayout prl = (BrushRequestLayout) findViewById(R.id.prl);
prl.setOnRefreshListener(new BrushRequestLayout.OnRefreshListener() {
@Override
public void onRefresh() {
prl.setRefreshing(false);
}
});
(6)加載監聽
BrushRequestLayout prl = (BrushRequestLayout) findViewById(R.id.prl);
prl.setOnLoadListener(new BrushRequestLayout.OnLoadListener() {
@Override
public void onLoad() {
prl.setLoading(false);
}
});
源碼
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.AnimationDrawable;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;
import com.android.R;
/**
* Created by Relin
* on 2018-09-26.
*/
public class BrushRequestLayout extends FrameLayout {
//開始
public final int BRUSH_START = 1;
//過界
public final int BRUSH_BOUND = 2;
//停留
public final int BRUSH_REMAIN = 3;
//完成
public final int BRUSH_COMPLETE = 4;
//恢復
public final int BRUSH_RECOVERY = 0;
//佈局填充器
private LayoutInflater inflater;
//頭部
private View headerView;
private BrushHeader header;
//腳部
private View footerView;
private BrushFooter footer;
//頭部高度
private float headerHeight = dpToPx(50);
//腳部高度
private float footerHeight = dpToPx(50);
//內容視圖
private View contentView[];
//內容類型-列表
private AbsListView absListView;
//內容類型-ScrollView
private ScrollView scrollView;
//按下的座標
private float downX, downY;
//刷新移動距離
private float refreshMoveY = 0;
//加載移動距離
private float loadMoveY = 0;
//刷新停留距離
private float refreshRemainY = 0;
//加載停留距離
private float loadRemainY = 0;
//刷新是否可用
private boolean refreshEnable = true;
//加載是否可用
private boolean loadEnable = true;
//是否正在刷新
private boolean isRefreshing;
//是否正在加載
private boolean isLoading;
//刷新監聽
private OnRefreshListener refreshListener;
//加載監聽
private OnLoadListener loadListener;
//延時器
private TimerHandler timerHandler;
//頭部背景
private int headerBackgroundColor = Color.parseColor("#EDEDEE");
//頭部背景
private int footerBackgroundColor = Color.parseColor("#EDEDEE");
//停留時間
private int duration = 600;
//幀動畫
private AnimationDrawable animationDrawable;
private float movePercent;
public BrushRequestLayout(@NonNull Context context) {
super(context);
initAttrs(context, null);
}
public BrushRequestLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
}
public BrushRequestLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
}
private void initAttrs(Context context, AttributeSet attrs) {
timerHandler = new TimerHandler();
inflater = LayoutInflater.from(context);
header = new BrushHeader().findViews();
footer = new BrushFooter().findViews();
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BrushRequestLayout);
headerHeight = typedArray.getDimension(R.styleable.BrushRequestLayout_headerHeight, headerHeight);
footerHeight = typedArray.getDimension(R.styleable.BrushRequestLayout_footerHeight, footerHeight);
refreshEnable = typedArray.getBoolean(R.styleable.BrushRequestLayout_refreshEnable, refreshEnable);
loadEnable = typedArray.getBoolean(R.styleable.BrushRequestLayout_refreshEnable, loadEnable);
duration = typedArray.getInt(R.styleable.BrushRequestLayout_duration, duration);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//內容
contentView = new View[getChildCount()];
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i) instanceof ScrollView) {
scrollView = (ScrollView) getChildAt(i);
}
if (getChildAt(i) instanceof AbsListView) {
absListView = (AbsListView) getChildAt(i);
}
contentView[i] = getChildAt(i);
}
//頭部
LayoutParams headerParams = new LayoutParams(LayoutParams.MATCH_PARENT, (int) headerHeight);
headerView.setLayoutParams(headerParams);
headerView.setBackgroundColor(Color.CYAN);
headerView.setBackgroundColor(headerBackgroundColor);
addView(headerView);
//腳部
LayoutParams footerParams = new LayoutParams(LayoutParams.MATCH_PARENT, (int) footerHeight);
footerParams.gravity = Gravity.BOTTOM;
footerView.setLayoutParams(footerParams);
footerView.setBackgroundColor(footerBackgroundColor);
addView(footerView);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
headerView.getLayoutParams().height = ((int) refreshRemainY + (int) refreshMoveY);
footerView.getLayoutParams().height = Math.abs((int) loadRemainY + (int) loadMoveY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e("Life", "onMeasure");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//頭部
MarginLayoutParams headerParams = (MarginLayoutParams) headerView.getLayoutParams();
left = getPaddingLeft() + headerParams.leftMargin;
top = getPaddingTop() + headerParams.topMargin - headerView.getMeasuredHeight() + (int) refreshRemainY + (int) refreshMoveY;
right = left + headerView.getMeasuredWidth();
bottom = top + headerView.getMeasuredHeight();
headerView.layout(left, top, right, bottom);
//內容區域
for (int i = 0; i < contentView.length; i++) {
View child = contentView[i];
MarginLayoutParams childParams = (MarginLayoutParams) child.getLayoutParams();
left = getPaddingLeft() + childParams.leftMargin;
top = getPaddingTop() + childParams.topMargin + (int) refreshMoveY + (int) refreshRemainY + (int) loadRemainY + (int) loadMoveY;
right = left + child.getMeasuredWidth();
bottom = top + child.getMeasuredHeight();
child.layout(left, top, right, bottom);
}
//腳部
MarginLayoutParams footerParams = (MarginLayoutParams) footerView.getLayoutParams();
left = getPaddingLeft() + footerParams.leftMargin;
top = getMeasuredHeight() + getPaddingTop() + footerParams.topMargin + (int) loadRemainY + (int) loadMoveY;
right = left + footerView.getMeasuredWidth();
bottom = top + footerView.getMeasuredHeight();
footerView.layout(left, top, right, bottom);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float moveY = event.getY() - downY;
float moveX = event.getX() - downX;
if (Math.abs(moveY) < moveX) {
return super.onInterceptTouchEvent(event);
}
if (moveY > 0 && refreshEnable) {
return isContentViewRefreshEnable();
}
if (moveY < 0 && loadEnable) {
return isContentViewLoadEnable();
}
break;
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (isRefreshing) {
//刷新可用就停留,不可用就不停留
if (refreshListener != null && refreshMoveY > 0 && refreshEnable) {
refreshListener.onRefresh();
}
if (refreshEnable) {
refreshRemainY = headerHeight;
refreshMoveY = 0;
isRefreshing = true;
showBrushViewState(BRUSH_REMAIN, true, false);
requestLayout();
} else {
refreshMoveY = 0;
refreshRemainY = 0;
showBrushViewState(BRUSH_RECOVERY, true, false);
requestLayout();
}
return true;
}
if (isLoading) {
loadRemainY = loadEnable ? -footerView.getMeasuredHeight() : 0;
if (loadListener != null && loadMoveY < 0 && loadEnable) {
loadListener.onLoad();
}
if (loadEnable) {
loadRemainY = -footerHeight;
loadMoveY = 0;
isLoading = true;
showBrushViewState(BRUSH_REMAIN, false, true);
requestLayout();
} else {
loadMoveY = 0;
loadRemainY = 0;
showBrushViewState(BRUSH_RECOVERY, false, true);
requestLayout();
}
return true;
}
break;
case MotionEvent.ACTION_MOVE:
float moveY = event.getY() - downY;
//正在刷新不能上拉加載,正在加載不能下拉刷新
if (isRefreshing && moveY < 0 || isLoading && moveY > 0) {
break;
}
//利用屏幕的1/5做界限
int each = 5;
movePercent = (moveY) / (getMeasuredHeight() / each);
if (Math.abs(movePercent) > each) {
break;
}
if (moveY > 0) {//下滑
headerView.setScaleX(1);
headerView.setScaleY(1);
refreshEnable = Math.abs(movePercent) >= 1;
refreshMoveY = moveY;
isRefreshing = true;
isLoading = false;
showBrushViewState(movePercent >= 1 ? BRUSH_BOUND : BRUSH_START, true, false);
} else {//上滑
footerView.setScaleX(1);
footerView.setScaleY(1);
loadEnable = Math.abs(movePercent) >= 1;
loadMoveY = moveY;
isRefreshing = false;
isLoading = true;
showBrushViewState(Math.abs(movePercent) >= 1 ? BRUSH_BOUND : BRUSH_START, false, true);
}
if (refreshEnable || loadEnable) {
requestLayout();
}
break;
}
return super.onTouchEvent(event);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (timerHandler != null) {
timerHandler.removeCallbacksAndMessages(null);
}
animationDrawable.stop();
animationDrawable = null;
}
public float dpToPx(float dp) {
return dp * getScreenDensity();
}
public float getScreenDensity() {
return Resources.getSystem().getDisplayMetrics().density;
}
public interface OnRefreshListener {
void onRefresh();
}
/**
* 設置刷新監聽
*
* @param refreshListener
*/
public void setOnRefreshListener(OnRefreshListener refreshListener) {
this.refreshListener = refreshListener;
if (isRefreshing) {
setRefreshing(isRefreshing);
}
}
public interface OnLoadListener {
void onLoad();
}
/**
* 設置加載監聽
*
* @param loadListener
*/
public void setOnLoadListener(OnLoadListener loadListener) {
this.loadListener = loadListener;
if (isLoading) {
setLoading(isLoading);
}
}
/**
* 設置正在刷新
*
* @param isRefreshing 是否開始刷新
*/
public void setRefreshing(boolean isRefreshing) {
this.isRefreshing = isRefreshing;
if (!isRefreshing) {
timerHandler.sendEmptyMessageDelayed(BRUSH_COMPLETE, duration);
} else {
refreshRemainY = headerHeight;
if (refreshListener != null) {
refreshListener.onRefresh();
}
}
}
/**
* 設置正在加載
*
* @param isLoading 是否開始加載
*/
public void setLoading(boolean isLoading) {
this.isLoading = isLoading;
if (!isLoading) {
timerHandler.sendEmptyMessageDelayed(BRUSH_COMPLETE, 500);
} else {
loadRemainY = -footerHeight;
if (loadListener != null) {
loadListener.onLoad();
}
}
}
/**
* 顯示狀態視圖
*
* @param state 狀態
* @param isRefresh 是否是下拉刷新
* @param isLoad 是否是上拉加載
*/
private void showBrushViewState(int state, boolean isRefresh, boolean isLoad) {
switch (state) {
case BRUSH_START:
if (isRefresh) {
header.iv_refresh_arrow.setRotation(360F);
header.tv_refresh_state.setText("下拉刷新");
}
if (isLoad) {
footer.iv_load_arrow.setRotation(360F);
footer.tv_load_state.setText("上拉加載");
}
break;
case BRUSH_BOUND:
if (isRefresh) {
header.iv_refresh_arrow.setRotation(180F);
header.tv_refresh_state.setText("釋放刷新");
}
if (isLoad) {
footer.iv_load_arrow.setRotation(180F);
footer.tv_load_state.setText("釋放加載");
}
break;
case BRUSH_REMAIN:
if (isRefresh) {
header.iv_refresh_arrow.setVisibility(GONE);
header.iv_refresh_state.setVisibility(GONE);
header.iv_refresh_loading.setVisibility(VISIBLE);
animationDrawable = (AnimationDrawable) header.iv_refresh_loading.getBackground();
animationDrawable.start();
header.tv_refresh_state.setText("刷新數據");
}
if (isLoad) {
footer.iv_load_arrow.setVisibility(GONE);
footer.iv_load_state.setVisibility(GONE);
footer.iv_load_loading.setVisibility(VISIBLE);
AnimationDrawable animationDrawable = (AnimationDrawable) footer.iv_load_loading.getBackground();
animationDrawable.start();
footer.tv_load_state.setText("刷新數據");
}
break;
case BRUSH_COMPLETE:
if (isRefresh) {
header.iv_refresh_arrow.setVisibility(GONE);
header.iv_refresh_state.setVisibility(VISIBLE);
header.iv_refresh_loading.setVisibility(GONE);
header.tv_refresh_state.setText("刷新成功");
}
if (isLoad) {
footer.iv_load_arrow.setVisibility(GONE);
footer.iv_load_state.setVisibility(VISIBLE);
footer.iv_load_loading.setVisibility(GONE);
header.tv_refresh_state.setText("加載成功");
}
break;
case BRUSH_RECOVERY:
refreshRemainY = 0;
loadRemainY = 0;
refreshMoveY = 0;
loadMoveY = 0;
isRefreshing = false;
isLoading = false;
refreshEnable = true;
loadEnable = true;
if (isRefresh) {
header.iv_refresh_arrow.setVisibility(VISIBLE);
header.iv_refresh_loading.setVisibility(GONE);
header.iv_refresh_state.setVisibility(GONE);
}
if (isLoad) {
footer.iv_load_arrow.setVisibility(VISIBLE);
footer.iv_load_state.setVisibility(GONE);
footer.iv_load_loading.setVisibility(GONE);
}
break;
}
requestLayout();
}
/**
* 內容去是否可以刷新
*
* @return
*/
private boolean isContentViewRefreshEnable() {
if (scrollView != null && scrollView.getScrollY() > 0) {
return false;
}
if (absListView != null && absListView.getFirstVisiblePosition() > 0) {
return false;
}
return true;
}
/**
* 內容區域是否可以加載
*
* @return
*/
private boolean isContentViewLoadEnable() {
if (scrollView != null && scrollView.getScrollY() > 0) {
return false;
}
if (absListView.getLastVisiblePosition() < (absListView.getCount() - 1)) {
return false;
}
return true;
}
class BrushHeader {
ImageView iv_refresh_arrow;
ImageView iv_refresh_loading;
ImageView iv_refresh_state;
TextView tv_refresh_state;
public BrushHeader() {
headerView = inflater.inflate(R.layout.android_brush_header, null);
}
public BrushHeader findViews() {
iv_refresh_arrow = headerView.findViewById(R.id.iv_refresh_arrow);
iv_refresh_loading = headerView.findViewById(R.id.iv_refresh_loading);
iv_refresh_state = headerView.findViewById(R.id.iv_refresh_state);
tv_refresh_state = headerView.findViewById(R.id.tv_refresh_state);
return this;
}
public View view() {
return headerView;
}
}
class BrushFooter {
ImageView iv_load_arrow;
ImageView iv_load_loading;
ImageView iv_load_state;
TextView tv_load_state;
public BrushFooter() {
footerView = inflater.inflate(R.layout.android_brush_footer, null);
}
public BrushFooter findViews() {
iv_load_arrow = footerView.findViewById(R.id.iv_load_arrow);
iv_load_loading = footerView.findViewById(R.id.iv_load_loading);
iv_load_state = footerView.findViewById(R.id.iv_load_state);
tv_load_state = footerView.findViewById(R.id.tv_load_state);
return this;
}
public View view() {
return footerView;
}
}
class TimerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case BRUSH_COMPLETE:
if (isRefreshing) {
showBrushViewState(BRUSH_COMPLETE, true, false);
}
if (isLoading) {
showBrushViewState(BRUSH_COMPLETE, false, true);
}
timerHandler.sendEmptyMessageDelayed(BRUSH_RECOVERY, duration);
break;
case BRUSH_RECOVERY:
if (isRefreshing) {
showBrushViewState(BRUSH_RECOVERY, true, false);
}
if (isLoading) {
showBrushViewState(BRUSH_RECOVERY, false, true);
}
break;
}
}
}
public void setHeaderHeight(float headerHeight) {
this.headerHeight = dpToPx(headerHeight);
}
public void setFooterHeight(float footerHeight) {
this.footerHeight = dpToPx(footerHeight);
}
public void setRefreshEnable(boolean refreshEnable) {
this.refreshEnable = refreshEnable;
}
public void setLoadEnable(boolean loadEnable) {
this.loadEnable = loadEnable;
}
public void setHeaderBackgroundColor(int headerBackgroundColor) {
this.headerBackgroundColor = headerBackgroundColor;
}
public void setFooterBackgroundColor(int footerBackgroundColor) {
this.footerBackgroundColor = footerBackgroundColor;
}
public void setDuration(int duration) {
this.duration = duration;
}
}