Android實(shí)踐之帶加載效果的下拉刷新上拉加載更多
前言
之前寫的一個(gè)LoadingBar,這次把LoadingBar加到下拉刷新的頭部。從頭寫一個(gè)下拉刷新,附贈上拉加載更多。下面話不多說了,來一起看看詳細(xì)的介紹吧。
效果圖:

實(shí)現(xiàn)過程
首先是自定義屬性,attrs.xml中定義頭部的高度和上下的padding。
####attrs.xml####
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="PPRefreshView_header"> <attr name="header_height" format="dimension"/> <attr name="header_padding" format="dimension"/> </declare-styleable> </resources>
然后是頭部的文件,里面放了一個(gè)TextView和一個(gè)圖片
header_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="50dp" android:orientation="vertical"> <ImageView android:src="@mipmap/down" android:layout_centerVertical="true" android:id="@+id/icon" android:layout_width="20dp" android:layout_height="20dp" android:layout_toLeftOf="@+id/text" android:layout_marginRight="5dp"/> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="下拉刷新" /> </RelativeLayout>
然后是布局文件,讓PPRefreshView作為父布局,下面可以放AbsListView的子類。
activity_main.xml####
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ppRefreshView="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" tools:context="top.greendami.greendami.MainActivity"> <top.greendami.greendami.PPRefreshView ppRefreshView:header_height="50dp" ppRefreshView:header_padding="10dp" android:id="@+id/swipeRefreshLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#d4d4d4"> <ListView android:background="@color/white" android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent"></ListView> </top.greendami.greendami.PPRefreshView> </RelativeLayout>
最后是重點(diǎn),下拉刷新的控件。
"精心"準(zhǔn)備了一張圖

####PPRefreshView.java####
package top.greendami.greendami;
package com.allrun.arsmartelevatorformanager.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.TextView;
import com.allrun.arsmartelevatorformanager.R;
import com.allrun.arsmartelevatorformanager.util.DPUnitUtil;
/**
* Created by GreendaMI on 2017/3/21.
*/
public class PPRefreshView extends ViewGroup {
Context context;
RecyclerView mListView;
PPView mPPView;
View header;
TextView title;
ImageView mImage;//箭頭
int listTop = 0;
float headerHeight = 10 + 30 + 10;//header的高度,上留白 + 文字(PPVIew)高度 + 下留白
float headerpadding = 10;//上留白,下留白
private int mYDown, mLastY;
//最短滑動距離
int a = 0;
RotateAnimation mRotateAnimation;
int state = 0; //0,正常;1,下拉;2,松開
public void setPPRefreshViewListener(PPRefreshViewListener mPPRefreshViewListener) {
this.mPPRefreshViewListener = mPPRefreshViewListener;
}
PPRefreshViewListener mPPRefreshViewListener;
public PPRefreshView(Context context) {
super(context);
this.context = context;
}
public PPRefreshView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
//px轉(zhuǎn)dp
a = DPUnitUtil.px2dip(context,ViewConfiguration.get(context).getScaledDoubleTapSlop());
TypedArray b = context.obtainStyledAttributes(attrs, R.styleable.PPRefreshView_header);
headerHeight = b.getDimension(R.styleable.PPRefreshView_header_header_height, 100);
headerpadding = b.getDimension(R.styleable.PPRefreshView_header_header_padding, 10);
b.recycle();
initAnima();
}
private void initAnima() {
//箭頭旋轉(zhuǎn)
mRotateAnimation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
mRotateAnimation.setFillAfter(true);
mRotateAnimation.setDuration(200);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mPPView != null) {
mPPView.measure(widthMeasureSpec,(int) (headerHeight- 2 * headerpadding));
}
if (header != null) {
header.measure(widthMeasureSpec, (int) (headerHeight- 2 * headerpadding));
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mListView == null && getChildCount() == 1) {
mListView = (RecyclerView)getChildAt(0);
mListView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(!recyclerView.canScrollVertically(1)){
//添加外部回調(diào)
if(mPPRefreshViewListener != null){ mPPRefreshViewListener.LoadMore();
}
}
}
});
}
if (mListView != null) {
mListView.layout(l, listTop, getMeasuredWidth(), b);
}
if (mPPView != null) {
//top:文字(PPVIew)高度 + 下留白
mPPView.layout(l, (int)(listTop - headerHeight + headerpadding), r, listTop);
}
if (header != null) {
//top:文字(PPView)高度 + 下留白
header.layout(l, (int)(listTop - headerHeight + headerpadding), r, listTop);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//松開手指,list回到頂部
if (state == 2) {
listTop = listTop - 25;
if (listTop < headerHeight) {
listTop = (int)headerHeight;
}
requestLayout();
}
//刷新完畢,關(guān)閉header
if (state == 0 && listTop > 0) {
listTop = listTop - 25;
if (listTop < 0) {
listTop = 0;
}
requestLayout();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 按下
mYDown = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 移動
mLastY = (int) event.getRawY();
if (!mListView.canScrollVertically(-1) && mLastY > mYDown &&(mLastY - mYDown) > a) {
state = 1;
listTop = mLastY - mYDown;
if (mPPView != null) {
removeView(mPPView);
mPPView = null;
}
if (header == null) {
header = LayoutInflater.from(context).inflate(R.layout.header_layout, null, false);
title = ((TextView) header.findViewById(R.id.text));
mImage = ((ImageView) header.findViewById(R.id.icon));
addView(header);
}
if (title != null && (mLastY - mYDown) > a * 2f) {
title.setText("松開刷新");
if (mImage.getAnimation() == null) {
mImage.startAnimation(mRotateAnimation);
}
}
if (title != null && (mLastY - mYDown) < a * 2f) {
title.setText("下拉刷新");
mImage.setImageResource(R.mipmap.down);
}
requestLayout();
//已經(jīng)判斷是下拉刷新,攔截手勢
return false;
}
break;
case MotionEvent.ACTION_UP:
// 抬起
// if (canLoad()) {
// loadData();
// }
//松手的時(shí)候,把文字標(biāo)題去掉
if (header != null) {
removeView(header);
header = null;
}
//如果之前是下拉狀態(tài),就添加PPVIew
if (mPPView == null && state == 1) {
//添加外部回調(diào)
if(mPPRefreshViewListener != null){
mPPRefreshViewListener.onRefresh();
}
mPPView = new PPView(context);
addView(mPPView);
mYDown = 0;
mLastY = 0;
state = 2;
requestLayout();
}
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
/**
* 收起下拉刷新的header,刷新結(jié)束
*/
public void RefreshOver() {
if (mPPView != null) {
removeView(mPPView);
mPPView = null;
}
if (header != null) {
removeView(header);
header = null;
title = null;
mImage = null;
}
state = 0;
}
public void setRefreshing(boolean b) {
if(!b){
state = 0;
postInvalidate();
}else{
state = 2;
postInvalidate();
}
}
public interface PPRefreshViewListener{
void onRefresh();
void LoadMore();
}
}
主要思路是監(jiān)聽手勢的滑動距離,如果ListView已經(jīng)劃到頂部,則ListView跟隨手指位置,并添加Header。放開手指后,ListView慢慢回到頂部。
外部回調(diào)。監(jiān)聽下拉和上拉。
mSwipeRefreshLayout.setPPRefreshViewListener(new PPRefreshView.PPRefreshViewListener() {
@Override
public void onRefresh() {
Toast.makeText(MainActivity.this,"親,刷新了",Toast.LENGTH_SHORT).show();
data.add("測試數(shù)據(jù)100");
mAdapter.notifyDataSetChanged();
}
@Override
public void LoadMore() {
Toast.makeText(MainActivity.this,"加載更多",Toast.LENGTH_SHORT).show();
}
});
refreshLayout.setRefreshing(false);;//刷新完畢
差點(diǎn)忘了粘連小球的View。
####PPView.java####
package top.greendami.greendami;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by GreendaMi on 2017/3/17.
*/
public class PPView extends View {
String TAG = "PPView";
//動畫開關(guān)
boolean isLoading = true;
Context mContext;
private int mWidth = 100;
private int mheight = 100;
public int mColor;
public Paint mPaint = new Paint();
float time = 0;
//小球與中間打球的最遠(yuǎn)距離
float distance = 100;
public PPView(Context context) {
super(context);
mContext = context;
mColor = context.getResources().getColor(R.color.colorPrimary);
init();
}
public PPView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
mColor = context.getResources().getColor(R.color.colorPrimary);
init();
}
private void init() {
mPaint.setAntiAlias(true);
mPaint.setColor(mColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//寬度至少是高度的4倍
if (widthSpecSize < 4 * heightSpecSize) {
widthSpecSize = 4 * heightSpecSize;
}
mWidth = widthSpecSize;
mheight = heightSpecSize;
distance = 1.2f * mheight;
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isLoading) {
//大圓半徑
float bigR = mheight * 0.32f + mheight * 0.03f * Math.abs((float) Math.sin(Math.toRadians(time)));
float smallR = mheight * 0.17f + mheight * 0.03f * Math.abs((float) Math.cos(Math.toRadians(time)));
float bigx = (getWidth()) / 2;
//畫中間大圓
canvas.drawCircle(bigx, mheight / 2, bigR, mPaint);
float smalx = getSmallCenterX();
//畫小圓
canvas.drawCircle(smalx, mheight / 2, smallR, mPaint);
//畫鏈接
//小球在右側(cè)
if (smalx > bigx) {
Path path = new Path();
//上面的貝塞爾曲線的第一個(gè)點(diǎn),在大圓身上
float x1 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
float y1 = mheight / 2 - bigR * (float) Math.sin(Math.toRadians(time));
if (y1 > mheight / 2 - smallR) {
y1 = mheight / 2 - smallR;
x1 = bigx + (float) (Math.sqrt(bigR * bigR - smallR * smallR));
}
//上面的貝塞爾曲線的第三個(gè)點(diǎn),在小圓身上
float x2 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
float y2 = mheight / 2 - smallR * (float) Math.sin(Math.toRadians(time));
if (y2 > mheight / 2 - smallR * 0.8) {
y2 = mheight / 2 - smallR * 0.8f;
x2 = smalx - smallR * (float) (Math.sqrt(1-0.64f));
}
//下面的貝塞爾曲線的第三個(gè)點(diǎn),在小圓身上
float x3 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
float y3 = mheight / 2 + smallR * (float) Math.sin(Math.toRadians(time));
if (y3 < mheight / 2 + smallR * 0.8) {
y3 = mheight / 2 + smallR * 0.8f;
x3 = smalx - smallR * (float) (Math.sqrt(1-0.64f));
}
//下面的貝塞爾曲線的第一個(gè)點(diǎn),在大圓身上
float x4 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
float y4 = mheight / 2 + bigR * (float) Math.sin(Math.toRadians(time));
if (y4 < mheight / 2 + smallR) {
y4 = mheight / 2 + smallR;
x4 = bigx + (float) (Math.sqrt(bigR * bigR - smallR * smallR));
}
path.moveTo(x1, y1);
path.quadTo((bigx + smalx) / 2, mheight / 2, x2, y2);
// 繪制貝賽爾曲線(Path)
path.lineTo(x3, y3);
path.quadTo((bigx + smalx) / 2, mheight / 2, x4, y4);
canvas.drawPath(path, mPaint);
}
//小球在左側(cè)
if (smalx < bigx) {
Path path = new Path();
float x1 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
float y1 = mheight / 2 - bigR * (float) Math.sin(Math.toRadians(time));
if (y1 > mheight / 2 - smallR) {
y1 = mheight / 2 - smallR;
x1 = bigx - (float) (Math.sqrt(bigR * bigR - smallR * smallR));
}
float x2 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
float y2 = mheight / 2 - smallR * (float) Math.sin(Math.toRadians(time));
if (y2 > mheight / 2 - smallR * 0.8) {
y2 = mheight / 2 - smallR * 0.8f;
x2 = smalx + smallR * (float) (Math.sqrt(1-0.64f));
}
float x3 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
float y3 = mheight / 2 + smallR * (float) Math.sin(Math.toRadians(time));
if (y3 < mheight / 2 + smallR * 0.8) {
y3 = mheight / 2 + smallR * 0.8f;
x3 = smalx + smallR * (float) (Math.sqrt(1-0.64f));
}
float x4 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
float y4 = mheight / 2 + bigR * (float) Math.sin(Math.toRadians(time));
if (y4 < mheight / 2 + smallR) {
y4 = mheight / 2 + smallR;
x4 = bigx - (float) (Math.sqrt(bigR * bigR - smallR * smallR));
}
path.moveTo(x1, y1);
path.quadTo((bigx + smalx) / 2, mheight / 2, x2, y2);
// 繪制貝賽爾曲線(Path)
path.lineTo(x3, y3);
path.quadTo((bigx + smalx) / 2, mheight / 2, x4, y4);
canvas.drawPath(path, mPaint);
}
postInvalidate();
}
}
//計(jì)算小球的X坐標(biāo)
private float getSmallCenterX() {
//此處控制速度
time = time + 4f;
return mWidth / 2 + distance * (float) Math.cos(Math.toRadians(time));
}
}
兩張素材


總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- Android實(shí)現(xiàn)簡單的下拉刷新控件
- android RecycleView實(shí)現(xiàn)下拉刷新和上拉加載
- Android自定義View仿騰訊TIM下拉刷新View
- Android巧用XListView實(shí)現(xiàn)萬能下拉刷新控件
- Android自定義控件ListView下拉刷新的代碼
- Android ExpandableListView實(shí)現(xiàn)下拉刷新和加載更多效果
- Android 使用SwipeRefreshLayout控件仿抖音做的視頻下拉刷新效果
- android 有阻尼下拉刷新列表的實(shí)現(xiàn)方法
- android使用SwipeRefreshLayout實(shí)現(xiàn)ListView下拉刷新上拉加載
- Android 實(shí)現(xiàn)的下拉刷新效果
相關(guān)文章
Android UI控件ExpandableListView基本用法詳解
這篇文章主要為大家詳細(xì)介紹了Android UI控件ExpandableListView基本用法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android開發(fā)之圖形圖像與動畫(三)Animation效果的XML實(shí)現(xiàn)
使用XML來定義Tween Animation動畫的XML文件在工程中res/anim目錄,這個(gè)文件必須包含一個(gè)根元素,感興趣的友可以了解一下,希望本文對你有所幫助2013-01-01
Android中TextView自動適配文本大小的幾種解決方案
在布局中使用的話,注意按照你最大的設(shè)備來設(shè)置字體大小,這樣在小設(shè)備上回自動縮放,下面這篇文章主要給大家介紹了關(guān)于Android中TextView自動適配文本大小的幾種解決方案,需要的朋友可以參考下2022-06-06
Android開發(fā)自定義控件之折線圖實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Android開發(fā)自定義控件之折線圖實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了Android自定義控件中折線圖原理、實(shí)現(xiàn)方法與操作注意事項(xiàng),需要的朋友可以參考下2020-05-05
Android發(fā)布項(xiàng)目到j(luò)itpack的完整步驟
這篇文章主要給大家介紹了關(guān)于Android發(fā)布項(xiàng)目到j(luò)itpack的完整步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
點(diǎn)九圖片的顯示內(nèi)容區(qū)域應(yīng)作何理解
.9 ,是andriod平臺的應(yīng)用軟件開發(fā)里的一種特殊的圖片形式,文件擴(kuò)展名為:.9.png;點(diǎn)九圖片的拉伸區(qū)域不難理解,顯示內(nèi)容區(qū)域是怎樣的,接下來本文為您一一解答,感興趣的朋友可以了解下2013-01-01
Android開發(fā)EditText禁止輸入監(jiān)聽及InputFilter字符過濾
這篇文章主要為大家介紹了Android開發(fā)EditText禁止輸入監(jiān)聽及InputFilter字符過濾示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06

