1. 程式人生 > >android 控制元件 下拉重新整理 phoenix 帶原始碼分析

android 控制元件 下拉重新整理 phoenix 帶原始碼分析

向納什致敬,鳳凰城永遠的英雄!phoenix

Yalantis 致力於提供世界一流的 Android 和 iOS 應用開發服務,因一些

動畫很棒的開源庫為大家所熟知


Phoenix-Android 旨在提供一個簡單的可定製的下拉重新整理功能。

<com.hankkin.AnimationPullToRefreshDemo.PullToRefreshView  
        android:id="@+id/pull_to_refresh"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent">  
   
        <ListView  
            android:id="@+id/list_view"  
            android:divider="@null"  
            android:dividerHeight="0dp"  
            android:fadingEdge="none"  
            android:layout_width="match_parent"  
            android:layout_height="match_parent" />  
   
    </com.hankkin.AnimationPullToRefreshDemo.PullToRefreshView>  



由此我們可以知道,在這個下拉重新整理並不是重寫了listview,而是在listview的外面套了一層佈局,也就是說listview被新增到了Phoenix上,那麼我們就能知道Phoenix其實就是一個viewgroup,到這裡就差不多知道要重寫那幾個方法了,自定義viewgroup的話也就onlayout、onmeasure、ontouchevent(如果是自定義view的話一般就重寫onmeasure、ondraw、ontouchevent),整理到這裡我們就可以來看看那原始碼了。


原始碼分析

構造方法
onmeasure
onlayout
頭部動畫效果實現
構造方法

首先看他的構造方法

public PullToRefreshView(Context context, AttributeSet attrs) {
	super(context, attrs);
	TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RefreshView);
	final int type = a.getInteger(R.styleable.RefreshView_type, STYLE_SUN);
	a.recycle();

	mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
	mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
	mTotalDragDistance = Utils.convertDpToPixel(context, DRAG_MAX_DISTANCE);

	mRefreshView = new ImageView(context);

	setRefreshStyle(type);

	addView(mRefreshView);
	//保證ondraw會執行,如果是true的話ondraw不會執行
	setWillNotDraw(false);
	ViewCompat.setChildrenDrawingOrderEnabled(this, true);
}

public void setRefreshStyle(int type) {
	setRefreshing(false);
	switch (type) {
		case STYLE_SUN:
			mBaseRefreshView = new SunRefreshView(getContext(), this);
			break;
		default:
			throw new InvalidParameterException("Type does not exist");
	}
	mRefreshView.setImageDrawable(mBaseRefreshView);
}



在這裡setRefreshStyle其實就可以直接看成是給頭部的imageview設定顯示的內容,然後將這個imageview新增到viewgroup中,另外的就是一寫引數的初始化。簡單的說就是在這個已經包了一個listview的viewgroup中再新增一個imageview。



onMeasure

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);

	ensureTarget();
	if (mTarget == null)
		return;

	widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingRight() - getPaddingLeft(), MeasureSpec.EXACTLY);
	heightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
	mTarget.measure(widthMeasureSpec, heightMeasureSpec);
	mRefreshView.measure(widthMeasureSpec, heightMeasureSpec);
}

private void ensureTarget() {
	if (mTarget != null)
		return;
	if (getChildCount() > 0) {
		for (int i = 0; i < getChildCount(); i++) {
			View child = getChildAt(i);
			if (child != mRefreshView) {
				mTarget = child;
				mTargetPaddingBottom = mTarget.getPaddingBottom();
				mTargetPaddingLeft = mTarget.getPaddingLeft();
				mTargetPaddingRight = mTarget.getPaddingRight();
				mTargetPaddingTop = mTarget.getPaddingTop();
			}
		}
	}
}

一開始mTarget 是空的,然後到getChildCount方法,想一下這個時候這個viewgroup中也就兩個孩子,一個imageview,一個listview,ensureTarget的作用就是把listview的例項賦值給mTarget,以及給幾個padding賦值,隨後在onmeasure中設定imageview和listview與外層的viewgroup一樣大小。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

	ensureTarget();
	if (mTarget == null)
		return;

	int height = getMeasuredHeight();
	int width = getMeasuredWidth();
	int left = getPaddingLeft();
	int top = getPaddingTop();
	int right = getPaddingRight();
	int bottom = getPaddingBottom();

	mTarget.layout(left, top + mCurrentOffsetTop, left + width - right, top + height - bottom + mCurrentOffsetTop);
	mRefreshView.layout(left, top, left + width - right, top + height - bottom);
}

imageview和listview放在相同的位置。

ontouchevent和onInterceptTouchEvent

如果不知道上面兩個方法的關係,可以去看看另一篇文章(http://blog.csdn.net/u012806692/article/details/50820070),首先重寫的是攔截的方法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

	if (!isEnabled() || canChildScrollUp() || mRefreshing) {
		return false;
	}

	final int action = MotionEventCompat.getActionMasked(ev);

	switch (action) {
		case MotionEvent.ACTION_DOWN:
			setTargetOffsetTop(0, true);
			mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
			mIsBeingDragged = false;
			final float initialMotionY = getMotionEventY(ev, mActivePointerId);
			if (initialMotionY == -1) {
				return false;
			}
			mInitialMotionY = initialMotionY;
			break;
		case MotionEvent.ACTION_MOVE:
			if (mActivePointerId == INVALID_POINTER) {
				return false;
			}
			final float y = getMotionEventY(ev, mActivePointerId);
			if (y == -1) {
				return false;
			}
			final float yDiff = y - mInitialMotionY;
			if (yDiff > mTouchSlop && !mIsBeingDragged) {
				mIsBeingDragged = true;
			}
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			mIsBeingDragged = false;
			mActivePointerId = INVALID_POINTER;
			break;
		case MotionEventCompat.ACTION_POINTER_UP:
			onSecondaryPointerUp(ev);
			break;
	}

	return mIsBeingDragged;
}

如果listview沒有滑到最頂部或者還在載入重新整理中就不執行之後的程式碼,直接返回false,否則記錄按下位置的y座標,注意這裡還有多點觸控的知識,這裡不理解可以先不用管。到了action_move之後,如果開始滑動(也就是大於mTouchSlop )就攔截touch事件不傳遞給子view,直接執行自己的ontouchevent方法,這裡我們先不管多點觸控相關的直接簡單理解下,接下來看ontouchevent事件 
@Override 
public boolean onTouchEvent(@NonNull MotionEvent ev) {
    if (!mIsBeingDragged) {
        return super.onTouchEvent(ev);
    }


    final int action = MotionEventCompat.getActionMasked(ev);


    switch (action) {
        case MotionEvent.ACTION_MOVE: {
            final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
            if (pointerIndex < 0) {
                return false;
            }


            final float y = MotionEventCompat.getY(ev, pointerIndex);
            final float yDiff = y - mInitialMotionY;
            final float scrollTop = yDiff * DRAG_RATE;
            mCurrentDragPercent = scrollTop / mTotalDragDistance;
            if (mCurrentDragPercent < 0) {
                return false;
            }
            float boundedDragPercent = Math.min(1f, Math.abs(mCurrentDragPercent));
            float extraOS = Math.abs(scrollTop) - mTotalDragDistance;
            float slingshotDist = mTotalDragDistance;
            float tensionSlingshotPercent = Math.max(0,
                    Math.min(extraOS, slingshotDist * 2) / slingshotDist);
            float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
                    (tensionSlingshotPercent / 4), 2)) * 2f;
            float extraMove = (slingshotDist) * tensionPercent / 2;
            int targetY = (int) ((slingshotDist * boundedDragPercent) + extraMove);


            mBaseRefreshView.setPercent(mCurrentDragPercent, true);
            setTargetOffsetTop(targetY - mCurrentOffsetTop, true);
            break;
        }
        case MotionEventCompat.ACTION_POINTER_DOWN:
            final int index = MotionEventCompat.getActionIndex(ev);
            mActivePointerId = MotionEventCompat.getPointerId(ev, index);
            break;
        case MotionEventCompat.ACTION_POINTER_UP:
            onSecondaryPointerUp(ev);
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL: {
            if (mActivePointerId == INVALID_POINTER) {
                return false;
            }
            final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
            final float y = MotionEventCompat.getY(ev, pointerIndex);
            final float overScrollTop = (y - mInitialMotionY) * DRAG_RATE;
            mIsBeingDragged = false;
            if (overScrollTop > mTotalDragDistance) {
                setRefreshing(true, true);
            } else {
                mRefreshing = false;
                animateOffsetToStartPosition();
            }
            mActivePointerId = INVALID_POINTER;
            return false;
        }
    }


    return true;
}


程式碼比較多一步步看,上面我們已經執行到了move,在ontouchevent的move中計算了滑動的百分比,頭部有個預設的最大值,看目前的滑動舉例是他的百分之幾,滑動距離超過最大值時取100%,在case action_move中的程式碼看著挺多,最重要的就最後的幾句,設定頭部的顯示百分比,還有listview的偏移(相當於magin),隨後主要看action_up,在擡起中判斷滑動距離是否到了載入的那個指定距離,如果足夠了就載入,不夠就直接回到初始位置,大致流程就是這樣。接下來看看imageview中的內容,就是一個


頭部動畫效果

其實就是imageview中的內容,一開始其實我們留下了一個問題,回想一下,在onlayout和onmeasure中我們設定的imageview的大小和顯示位置和listview的是一樣的,那麼兩個不就疊在一起了嗎?接下來看下imageview的內容是什麼就明白了。它的構造方法就不看了,就一堆變數的賦值,載入圖片等等,直接看自定義view最重要的draw方法


@Override
public void draw(Canvas canvas) {
	if (mScreenWidth <= 0) return;

	final int saveCount = canvas.save();

	canvas.translate(0, mTop);
	canvas.clipRect(0, -mTop, mScreenWidth, mParent.getTotalDragDistance());

	drawSky(canvas);
	drawSun(canvas);
	drawTown(canvas);

	canvas.restoreToCount(saveCount);
}





其中將畫布移動到了mtop的位置,再看之前初始化的時候將其賦值為mTop = -mParent.getTotalDragDistance();, 
canvas.clipRect(0, -mTop, mScreenWidth, mParent.getTotalDragDistance()); 
這裡一開始是截取了一個高度為0的矩形,隨著move慢慢變大,後面的draw就只能在這上面操作。重要的就講完了,其他的包括怎麼根據百分比來改變屬性,這些其實可以自己發揮實現自己的效果。


示例程式碼:

mPullToRefreshView = (PullToRefreshView) findViewById(R.id.pull_to_refresh);
mPullToRefreshView.setOnRefreshListener(new PullToRefreshView.OnRefreshListener() {    
@Override
    public void onRefresh() {
        mPullToRefreshView.postDelayed(new Runnable() {           
 @Override
            public void run() {
                mPullToRefreshView.setRefreshing(false);
            }
        }, REFRESH_DELAY);
    }
 });


相關推薦

android 控制元件 重新整理 phoenix 原始碼分析

向納什致敬,鳳凰城永遠的英雄!phoenix Yalantis 致力於提供世界一流的 Android 和 iOS 應用開發服務,因一些 動畫很棒的開源庫為大家所熟知 Phoenix-Android 旨在提供一個簡單的可定製的下拉重新整理功能。 <com

Android SwipeRefreshLayout 官方重新整理控制元件介紹

                轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/24521483下面App基本都有下拉重新整理的功能,以前基本都使用XListView或者自己寫一個下拉重新整理,近期Google提供了一個官方的下拉重新整理控制

android自定義重新整理和上載入控制元件

import android.content.Context; import android.graphics.Point; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.NestedScro

Android SwipeRefreshLayout官方重新整理控制元件介紹(與知乎Android客戶端重新整理一樣!!)

轉載出處:http://blog.csdn.net/lmj623565791/article/details/24521483 //沒少看鴻洋老師的視訊,一如既往,鴻洋老師講解的淺顯易懂! 下面App基本都有下拉重新整理的功能,以前基本都使用XListView或者自己

Android——(高階控制元件框與搜尋框)

1. 高階控制元件與低階控制元件區別    是否使用介面卡 2. 介面卡種類和作用   2.1 種類       陣列介面卡 ArrayAdapter         new ArrayAdapt

Android第六次——(高階控制元件框與搜尋框)

1. 高階控制元件與低階控制元件區別    是否使用介面卡 2. 介面卡種類和作用   2.1 種類       陣列介面卡 ArrayAdapter         new ArrayAdapter<String>(this,R.layout.actv_sty

android之官方重新整理元件SwipeRefreshLayout

SwipeRefreshLayout是谷歌官方Sdk提供的下拉元件,繼承自ViewGroup,在support v4相容包下,但必須把你的support library的版本升級到19.1。先看看效果 SwipeRefreshLayout常用API 1、

Android快速開發控制元件---導航選單

LQRDropdownLayout 該專案是下拉導航選單,使用非常簡單。基於FilterDropDownMenu-master專案進行封裝,因為原專案的使用太複雜了,光佈局就得幾十行程式碼,如果專案中多處用到下拉選單,那程式碼的冗餘度就太大了,故本佈局對其進行

Android中ListView重新整理載入更多效果實現

  在Android開發中,下拉重新整理和上拉載入更多在很多app中都會有用到,下面就是具體的實現的方法。 首先,我們自定義一個RefreshListView來繼承與ListView,下面是程式碼: package com.example.downrefresh; import

頭部視覺視差(仿QQ控制元件效果)

看到新版QQ空間出來的時候,首頁的下拉的效果比較炫酷,下拉的時候會把整張圖片顯示出來,鬆開手之後就會自己恢復原狀,不說了,上程式碼。 這是效果圖 public class ParallaxListView extends ListView {//關於自定義控制元件//onmeasu

Android簡單的重新整理,上載入

先匯入第三方的東西 下載地址 匯入後,就和你的專案聯絡起來 佈局程式碼(activity_pull_to_refresh_action.xml) <?xml version="1.0" encoding="utf-8"?> <Lin

C# combox控制元件列表模糊查詢

//預設查詢全部,可以選擇取消 private void FrmAddShang_Load(object sender, EventArgs e) { SqlConnection conn = DB.lianjie();

C# winfrom Datagridview控制元件選單

拖拽一個datagridview放在介面,編輯列把下來選單那列ColumnType設定成DataGridViewComboBoxColumn 然後在資料一欄的Items可以寫下來選單的內容也可以後臺程式碼寫 下面是後臺程式碼實現功能 private void dgUserAuthData_Edi

Android LRecyclerView實現重新整理,滑動到底部自動載入更多

                     隨著功能的不斷優化,框架中的類或者介面名字會有變動,為了獲取準確的使用方法,請參考最新的說明文件:點此檢視。簡介LRecyclerView是支援addHeaderView、 addFooterView、下拉重新整理、分頁載入資料的RecyclerView。它對 Rec

自定義控制元件之組合式控制元件 選擇框

自定義控制元件之組合式控制元件 下拉選擇框 文章目錄 自定義控制元件之組合式控制元件 下拉選擇框 零 組合控制元件下載 一 自定義控制元件思路 二 MainActivity核心程式碼 三 activity_main.xml

Android中ListView重新整理的實現

ListView中的下拉重新整理是非常常見的,也是經常使用的,看到有很多同學想要,那我就整理一下,供大家參考。那我就不解釋,直接上程式碼了。 這裡需要自己重寫一下ListView,重寫程式碼如下: package net.loonggg.listview; impor

Android ListView 實現重新整理載入

1.簡介        無疑,在Android開發中,ListView是使用非常頻繁的控制元件之一,ListView提供一個列表的容易,允許我們以列表的形式將資料展示到介面上,但是Google給我們提供的原生ListView的控制元件,雖然在功能上很強大,但是在使用

Android--Google官方重新整理SwipeRefreshLayout(附加增加上載入)

Demo_SwipeRefreshLayout 下拉重新整理是用系統的控制元件,但是這控制元件本身不帶有上拉載入,上拉載入需要在ListView的Adapter中設定 需要V4包的支援 compile 'com.android.support:support-v4

jQuery UI Autocomplete控制元件列表固定高度

jQuery ui Autocomplete控制元件下拉列表高度預設是自適應的,有多少資料就會加載出來多少行。以下是給下拉列表設定一個預設高度並新增滾動條的解決辦法。 在樣式裡新增以下程式碼 .ui-autocomplete { max-he

Android控制元件postDelayed用法,View自的定時器

有一個需求是這樣的,點選加關注按鈕後,執行關注操作,成功後按鈕文字變為“已關注”,保持3秒,三秒後按鈕文字便問“取消關注”,點選後執行取消關注的操作 可以使用定時器實現,但是使用View的posyDelayed更加方便 原始碼如下: android.view.View