Android自定義View-下拉刷新控件
下拉刷新是android開發過程中很常見的功能,github上面有許多下拉刷新的開源控件可以使用。但有時候這些開源控件不能完全符合我們的項目要求,這時就需要自己進行修改,這時候我們就需要了解下拉刷新的原理,才能自由的修改它的功能,因此我自己寫了一個簡單的下拉刷新控件,以瞭解其原理
下拉刷新原理
下拉刷新控件主要由兩部分組成,內容部分,與下拉頭部分
其主要流程:初始時將下拉頭的位置設置到屏幕上方,當內容部分滑動到頂部時則根據手指滑動的y軸方向參數,將下拉頭滑動到屏幕中,當滑動距離達到某一個值時則可以刷新,這時手指鬆開,則進入刷新狀態,刷新完成之後回到初始位置
實現
大概原理已經清楚了,那我們就要開始實現一個下拉刷新控件了
先寫一個下拉頭的佈局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp">
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_centerInParent="true"
android:src="@mipmap/arrow"
/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="30dip"
android:layout_height="30dip"
android:layout_centerInParent="true"
android:visibility="gone"
/>
</RelativeLayout>
然後新建一個RefreshableView的類,這個繼承自LinearLayout,之所以繼承LinearLayout是因爲我們的下拉刷新控件是一個線性佈局的ViewGroup
添加下拉頭佈局
public RefreshableView(Context context, AttributeSet attrs) {
super(context, attrs);
//添加下拉頭佈局
header = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh , null , true);
arrow = (ImageView)header.findViewById(R.id.arrow);
progressBar = (ProgressBar)header.findViewById(R.id.progress_bar);
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOrientation(VERTICAL);
addView(header , 0);
mScroller = new Scroller(context);
}
在Layout時設置下拉頭的初始位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed && !loadOnce){
//下拉頭向上偏移,隱藏下拉頭
hideHeaderHeight = -header.getHeight();
((MarginLayoutParams)header.getLayoutParams()).topMargin = hideHeaderHeight;
//設置ListView的觸摸事件
listView = (ListView)getChildAt(1);
listView.setOnTouchListener(this);
loadOnce = true;
}
}
ListView滑動監聽的onTouch方法
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
//判斷ListView是否滑動到頂部
isAbleToPull(motionEvent);
if(ableToPull){
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
yDown = motionEvent.getRawY();
mLastY = motionEvent.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//手指上滑不處理事件
if(motionEvent.getRawY() - yDown < 0 && getScrollY() == 0){
return false;
}
//小於最小滑動距離
if(motionEvent.getRawY() - yDown < touchSlop){
return false;
}
//手指是下滑並且下拉頭完全隱藏執行下滑
if(motionEvent.getRawY() - yDown > 0 && getScrollY() >= 0){
//滑動距離大於下拉頭高度鬆手可刷新
if(getScrollY() < hideHeaderHeight){
currentStatus = STATUS_RELEASE_TO_REFRESH;
}else{
currentStatus = STATUS_PULL_TO_REFRESH;
}
scrollBy(0 , -(int)(motionEvent.getRawY() - mLastY));
}
mLastY = motionEvent.getRawY();
break;
case MotionEvent.ACTION_UP:
default:
if(currentStatus == STATUS_PULL_TO_REFRESH){
//隱藏下拉頭
smoothScrollBy( 0 , -getScrollY());
}
if(currentStatus == STATUS_RELEASE_TO_REFRESH){
//刷新
onRefresh();
}
break;
}
// 時刻記得更新下拉頭中的信息
if (currentStatus == STATUS_PULL_TO_REFRESH
|| currentStatus == STATUS_RELEASE_TO_REFRESH) {
updateHeaderView();
// 當前正處於下拉或釋放狀態,要讓ListView失去焦點,否則被點擊的那一項會一直處於選中狀態
listView.setPressed(false);
listView.setFocusable(false);
listView.setFocusableInTouchMode(false);
lastStatus = currentStatus;
// 當前正處於下拉或釋放狀態,通過返回true屏蔽掉ListView的滾動事件
return true;
}
}
return false;
}
isAbleToPull方法判斷ListView是否滑動到頂部
public void isAbleToPull(MotionEvent motionEvent){
View fristChiler = listView.getChildAt(0);
if(fristChiler != null) {
//如果ListView滑動到頂部可以下拉下拉頭
if (listView.getFirstVisiblePosition() == 0 && fristChiler.getTop() == 0){
if(!ableToPull) {
yDown = motionEvent.getRawY();
}
ableToPull = true;
}else{
ableToPull = false;
}
}else{
//ListView爲空,也可以下拉下拉頭
ableToPull = true;
}
}
rotateArrow方法根據當前狀態旋轉箭頭
private void rotateArrow() {
float pivotX = arrow.getWidth() / 2f;
float pivotY = arrow.getHeight() / 2f;
float fromDegrees = 0f;
float toDegrees = 0f;
if (currentStatus == STATUS_PULL_TO_REFRESH) {
fromDegrees = 180f;
toDegrees = 360f;
} else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
fromDegrees = 0f;
toDegrees = 180f;
}
RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);
animation.setDuration(100);
animation.setFillAfter(true);
arrow.startAnimation(animation);
}
updateHeaderView根據當前狀態更新下拉頭的信息
private void updateHeaderView() {
if (lastStatus != currentStatus) {
if (currentStatus == STATUS_PULL_TO_REFRESH) {
arrow.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
rotateArrow();
} else if (currentStatus == STATUS_RELEASE_TO_REFRESH) {
arrow.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
rotateArrow();
} else if (currentStatus == STATUS_REFRESHING) {
progressBar.setVisibility(View.VISIBLE);
arrow.clearAnimation();
arrow.setVisibility(View.GONE);
}
}
}
這段代碼實現平滑滑動
public void smoothScrollBy(int dx , int dy){
mScroller.startScroll( 0 , getScrollY() , 0 , dy , SCROLL_TIME);
invalidate();
}
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX() , mScroller.getCurrY());
postInvalidate();
}
}
定義一個下拉刷新監聽藉口
public interface PullToRefreshListener {
/**
* 刷新時會去回調此方法,在方法內編寫具體的刷新邏輯。注意此方法是在子線程中調用的, 你可以不必另開線程來進行耗時操作。
*/
void onRefresh();
}
刷新完成方法,設置刷新事件監聽器,刷新方法
//刷新結束後調用的方法
public void finishRefresh(){
currentStatus = STATUS_REFRESH_FINISHED;
smoothScrollBy( 0 , -getScrollY());
}
//設置刷新事件監聽
public void setRefreshListener(PullToRefreshListener l){
this.mListener = l;
}
//刷新方法
public void onRefresh(){
smoothScrollBy( 0 , (-getScrollY()) + hideHeaderHeight);
if(mListener != null){
mListener.onRefresh();
}
}
控件已經寫完了,接下來我們要看一下實現效果如果
在activity佈局文件中加入如下佈局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.example.pulltorefreshtest.RefreshableView
android:id="@+id/refreshable_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:id="@+id/list_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="none" >
</ListView>
</com.example.pulltorefreshtest.RefreshableView>
</RelativeLayout>
在MainActivity中設置下拉刷新事件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
refreshableView = (RefreshableView) findViewById(R.id.refreshable_view);
listView = (ListView) findViewById(R.id.list_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);
listView.setAdapter(adapter);
refreshableView.setRefreshListener(new RefreshableView.PullToRefreshListener() {
@Override
public void onRefresh() {
refreshableView.finishRefresh();
}
});
}