1. 程式人生 > >快速實現 ListView下拉,圖片放大重新整理操作

快速實現 ListView下拉,圖片放大重新整理操作

今天要寫的這個效果屬於重新整理類,比較實用,像很多流行的 app 都是用了這種效果,大家熟知的QQ空間、微博個人主頁等,這個效果在 github 上也有別人實現好的原始碼,點選檢視。這裡也參考了上面的原始碼;還是那句話,看 blog主要是學習其中的原理和思路。

動態效果圖
這裡寫圖片描述

圖片放大的原理是什麼呢?
通過改變圖片顯示控制元件 ImageView 的父控制元件的高度,比如這裡的頭部 View 是一個 FrameLayout,FrameLayout 中再 通過 add 方法把圖片 View 新增進去,addView(ImageView),ImageView有幾個屬性是要特別注意的,ImageView 的放縮型別為從中間擷取
setScaleType(ImageView.ScaleType.CENTER_CROP);
並且寬高設為匹配父控制元件;所以想要圖片有放大效果,只需設定 FrameLayout 的 LayoutParams 中的 height值,通過改變 height 的值從而改變 ImageView 的顯示高度。講的有點混亂,可以結合下面的程式碼來理解。

如果你是對手勢事件處理很瞭解的朋友,對這個效果的實現應該沒有什麼難度,唯一的一點就是判斷何時該放大圖片,何時該滾動ListView。
這裡就直接貼程式碼:

/**
* Created by gyzhong on 15/3/22.
*/
public class PullZoomListView extends ListView {

/*頭部View 的容器*/
private FrameLayout mHeaderContainer;
/*頭部View 的圖片*/
private ImageView mHeaderImg;
/*螢幕的高度*/
private int mScreenHeight;
/*螢幕的寬度*/
private int mScreenWidth;

private int mHeaderHeight;

/*無效的點*/
private static final int INVALID_POINTER = -1;
/*滑動動畫執行的時間*/
private static final int MIN_SETTLE_DURATION = 200; // ms
/*定義了一個時間插值器,根據ViewPage控制元件來定義的*/
private static final Interpolator sInterpolator = new Interpolator() {
    public float getInterpolation(float t) {
        t -= 1.0f;
        return t * t * t * t * t + 1.0f;
    }
};

/*記錄上一次手指觸控的點*/
private float mLastMotionX;
private float mLastMotionY;

/*當前活動的點Id,有效的點的Id*/
protected int mActivePointerId = INVALID_POINTER;
/*開始滑動的標誌距離*/
private int mTouchSlop;

/*放大的倍數*/
private float mScale;
/*上一次放大的倍數*/
private float mLastScale;

/*最大放大的倍數*/
private final float mMaxScale = 2.0f;
/*是否需要禁止ListView 的事件響應*/
private boolean isNeedCancelParent;

/*這個不解釋*/
private OnScrollListener mScrollListener ;

/*下拉重新整理的閾值*/
private final float REFRESH_SCALE = 1.20F;

/*下拉重新整理監聽*/
private OnRefreshListener mRefreshListener ;


public PullZoomListView(Context context) {
    super(context);
    init(context);
}

public PullZoomListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

public PullZoomListView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
}

private void init(Context context) {

    /*這裡獲取的是一個無用值,可忽略*/
    final ViewConfiguration configuration = ViewConfiguration.get(context);
    mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);

    /*建立頭部View 的容器*/
    mHeaderContainer = new FrameLayout(context);
    /*獲取螢幕的畫素值*/
    DisplayMetrics metrics = new DisplayMetrics();
    ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metrics);
    mScreenHeight = metrics.heightPixels;
    mScreenWidth = metrics.widthPixels;
    /*設定頭部View 的初始大小*/
    mHeaderHeight = (int) ((9 * 1.0f / 16) * mScreenWidth);
    LayoutParams absLayoutParams = new LayoutParams(mScreenWidth, mHeaderHeight);
    mHeaderContainer.setLayoutParams(absLayoutParams);
    /*建立圖片顯示的View*/
    mHeaderImg = new ImageView(context);
    FrameLayout.LayoutParams imgLayoutParams = new FrameLayout.LayoutParams
            (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    mHeaderImg.setScaleType(ImageView.ScaleType.CENTER_CROP);
    mHeaderImg.setLayoutParams(imgLayoutParams);
    mHeaderContainer.addView(mHeaderImg);

    /*增加頭部View*/
    addHeaderView(mHeaderContainer);
    /*設定監聽事件*/
    super.setOnScrollListener(new InternalScrollerListener() );

}
/*處理事件用*/

@Override
public boolean onTouchEvent(MotionEvent ev) {

    final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

    switch (action) {
        case MotionEvent.ACTION_DOWN:

            /*計算 x,y 的距離*/
            int index = MotionEventCompat.getActionIndex(ev);
            mActivePointerId = MotionEventCompat.getPointerId(ev, index);
            if (mActivePointerId == INVALID_POINTER)
                break;
            mLastMotionX = MotionEventCompat.getX(ev, index);
            mLastMotionY = MotionEventCompat.getY(ev, index);
            // 結束動畫,目前沒做處理,可忽略
            abortAnimation();
            /*計算算一次放縮的比例*/
            mLastScale = (this.mHeaderContainer.getBottom() / this.mHeaderHeight);
            /*當按下的時候把這個標誌為設為有效*/
            isNeedCancelParent = true ;
            break;
        case MotionEvent.ACTION_MOVE:
            int indexMove = MotionEventCompat.getActionIndex(ev);
            mActivePointerId = MotionEventCompat.getPointerId(ev, indexMove);

            if (mActivePointerId == INVALID_POINTER) {
                /*這裡相當於鬆手*/
                finishPull();
                isNeedCancelParent = true ;
            } else {
                /*這是一個關鍵值,通過這個值來判斷是否需要放大圖片*/
                if (mHeaderContainer.getBottom() >= mHeaderHeight) {
                    ViewGroup.LayoutParams params = this.mHeaderContainer
                            .getLayoutParams();
                    final float y = MotionEventCompat.getY(ev, indexMove);
                    float dy = y - mLastMotionY;
                    float f = ((y - this.mLastMotionY + this.mHeaderContainer
                            .getBottom()) / this.mHeaderHeight - this.mLastScale)
                            / 2.0F + this.mLastScale;
                    if ((this.mLastScale <= 1.0D) && (f <= this.mLastScale)) {
                        params.height = this.mHeaderHeight;
                        this.mHeaderContainer
                                .setLayoutParams(params);
                        return super.onTouchEvent(ev);
                    }
                    /*這裡設定緊湊度*/
                    dy = dy * 0.5f * (mHeaderHeight * 1.0f / params.height);
                    mLastScale = (dy + params.height) * 1.0f / mHeaderHeight;
                    mScale = clamp(mLastScale, 1.0f, mMaxScale);

// Log.v(“zgy”, “=======mScale=====” + mLastScale+”,f = “+f);

                    params.height = (int) (mHeaderHeight * mScale);
                    mHeaderContainer.setLayoutParams(params);
                    mLastMotionY = y;
                    /*這裡,如果圖片有放大,則遮蔽ListView 的其他事件響應*/
                    if(isNeedCancelParent ){
                        isNeedCancelParent = false;
                        MotionEvent motionEvent = MotionEvent.obtain(ev);
                        motionEvent.setAction(MotionEvent.ACTION_CANCEL);
                        super.onTouchEvent(motionEvent);
                    }
                    return true;
                }
                mLastMotionY = MotionEventCompat.getY(ev, indexMove);

            }

            break;
        case MotionEvent.ACTION_UP:
            /*結束事件響應,做相應的操作*/
            finishPull();

            break;
        case MotionEvent.ACTION_POINTER_UP:
            /*這裡需要注意,多點處理,這裡的處理方式是:如果有兩個手指按下,擡起的是後按下的手指,則不做處理
            * 如果擡起的是最先按下的手指,則復原圖片效果。
            * */
            int pointUpIndex = MotionEventCompat.getActionIndex(ev);
            int pointId = MotionEventCompat.getPointerId(ev, pointUpIndex);
            if (pointId == mActivePointerId) {
                /*鬆手執行結束拖拽操作*/
                /*結束事件響應,做相應的操作*/
                finishPull();
            }

            break;

    }

    return super.onTouchEvent(ev);
}

@Override
public void setOnScrollListener(OnScrollListener l) {
    mScrollListener = l ;
}

private void abortAnimation() {
    /*啥都沒做,暫時沒做而已*/
}

private void finishPull() {
    mActivePointerId = INVALID_POINTER;
    /*這是一個閾值,如果成立,則表示圖片已經放大了,在手指擡起的時候需要復原圖片*/
    if (mHeaderContainer.getBottom() > mHeaderHeight){

// Log.v(“zgy”, “===super====onTouchEvent========”);
/這裡是下拉重新整理的閾值,當達到了,則表示需要重新整理,可以新增重新整理動畫/
if (mScale > REFRESH_SCALE){
if (mRefreshListener != null){
mRefreshListener.onRefresh();
}
}
/圖片復原動畫/
pullBackAnimation();
}
}

/**
 * 這是屬性動畫的知識,不懂的可以去看看屬性動畫的知識
 */
private void pullBackAnimation(){
    ValueAnimator pullBack = ValueAnimator.ofFloat(mScale , 1.0f);
    pullBack.setInterpolator(sInterpolator);
    pullBack.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float value = (float) animation.getAnimatedValue();
            LayoutParams params = (LayoutParams) mHeaderContainer.getLayoutParams();
            params.height = (int) (mHeaderHeight * value);
            mHeaderContainer.setLayoutParams(params);
        }
    });
    pullBack.setDuration((long) (MIN_SETTLE_DURATION*mScale));
    pullBack.start();

}


/**
 * 通過事件和點的 id 來獲取點的索引
 *
 * @param ev
 * @param id
 * @return
 */
private int getPointerIndex(MotionEvent ev, int id) {
    int activePointerIndex = MotionEventCompat.findPointerIndex(ev, id);
    if (activePointerIndex == -1)
        mActivePointerId = INVALID_POINTER;
    return activePointerIndex;
}


public void setOnRefreshListener(OnRefreshListener l){
    mRefreshListener = l ;
}

public ImageView getHeaderImageView() {
    return this.mHeaderImg;
}


private float clamp(float value, float min, float max) {
    return Math.min(Math.max(value, min), max);
}

private class InternalScrollerListener implements OnScrollListener{
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

        if (mScrollListener != null){
            mScrollListener.onScrollStateChanged(view,scrollState);
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        float diff = mHeaderHeight - mHeaderContainer.getBottom();
        if ((diff > 0.0F) && (diff < mHeaderHeight)) {
            int i = (int) (0.3D * diff);
            mHeaderImg.scrollTo(0, -i);
        } else if (mHeaderImg.getScrollY() != 0) {
            mHeaderImg.scrollTo(0, 0);
        }

        Log.v("zgy","=========height==="+getScrolledY());

        if (mScrollListener != null){
            mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }
    }
}

public int getScrolledY() {
    View c = getChildAt(0);
    if (c == null) {
        return 0;
    }

    int firstVisiblePosition = getFirstVisiblePosition();
    int top = c.getTop();

    int headerHeight = 0;
    if (firstVisiblePosition >= 1) {
        headerHeight = mHeaderHeight;
    }

    return -top + firstVisiblePosition * c.getHeight() + headerHeight;
}

public interface OnRefreshListener {
   void onRefresh() ;
}

public void computeRefresh(){
    if (mActivePointerId != INVALID_POINTER){

    }
}

}

比較難理解的地方都做了註釋,所以。。。應該還是很好理解的。

總結:
今天 blog的一個難點就是,手勢的處理,下拉放大的條件判斷;針對這種問題,我也沒有很好的解決方案,我的經驗就是,直接通過 列印 Log 的方式來尋找規律,因為有的時候想的挺煩的,而且邏輯很容易混亂。
接著是圖片放大的原理,知道了就很好實現此功能。

原始碼

相關推薦

快速實現 ListView,圖片放大重新整理操作

今天要寫的這個效果屬於重新整理類,比較實用,像很多流行的 app 都是用了這種效果,大家熟知的QQ空間、微博個人主頁等,這個效果在 github 上也有別人實現好的原始碼,點選檢視。這裡也參考了上面的原始碼;還是那句話,看 blog主要是學習其中的原理和思路。

另類實現 ScrollView 頭部放大

一、前言 前兩天接到一個在列表下拉時頭部放大的需求,也就是這樣的效果: 二、思路 查了查資料,這樣的效果一般都是通過自定義 ScrollView 來實現,不過我總覺得這樣挺麻煩的,需要去修改原來的 XML 檔案,而且侷限性也挺大,需要縮放的控制元件必須放在第一個,在看了別人實現

Android 自定義view:實現ListView的視差特效

一、概述: 現在流型的APP如微信朋友圈,QQ空間,微博個人展示都有視差特效的影子。 如圖:下拉圖片會產生圖片拉昇的效果,放手後圖片有彈回到原處: 那我們如何實現呢? 1)重寫ListView控制元件: 2)重寫裡面的overScrollBy方法

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

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

ListView重新整理具體實現

1、現在gitHub中有ListView的下拉重新整理的框架,不過個人手賤,還是自己做了一個,懂了很多東西,哎,又不是資料結構,只是一些基本的下拉東西,在此留給自己回憶的。 2、不適宜新人學習,因為我基本不載入圖片在這,只留給有基礎的人忘記重新整理的一些重要東西檢視。 主佈局檔案:如果不

Android中ListView重新整理實現

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

react-native-page-listview使用方法(自定義FlatList/ListView重新整理,上載入更多,方便的實現分頁)

react-native-page-listview 對ListView/FlatList的封裝,可以很方便的分頁載入網路資料,還支援自定義下拉重新整理View和上拉載入更多的View.相容高版本FlatList和低版本ListVIew.元件會根據你使用的re

自定義ListView重新整理載入更多功能

本篇的自定義listview包含下拉重新整理和上拉載入更多都是自定義。如果你想把重新整理的圖片做的更炫只需要更換下圖片加上適當的動畫就OK咯!由於沒有合適的圖片就用了個粗糙的。不好看請見諒。 //部分程式碼(都做了註釋): /** * @author: ZQF_

ListView重新整理,上自動載入更多

更新2016-2-19 程式碼下載地址已經更新。因為程式碼很久沒更新,已經很落伍了,建議大家使用RecyclerView實現。 參考專案: https://github.com/bingoogolapple/BGARefreshLayout-Android https://

Android ListView 重新整理,上載入更多,帶動畫 自定義控制元件

之前每次 專案中用到ListView 的 下拉重新整理 以及上拉分頁載入 都是 用的 網上 下載 的 類庫, 使用起來 諸多不便 ,於是 趁著有空 ,自己封裝了ListView 讓其 實現 下拉重新整理,以及分頁載入功能。 以下是 效果圖: 當 滑動到 ListView 頂

ajax+php實現無限重新整理的功能

效果展示: <!DOCTYPE html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scal

Android重寫ScrollView實現回彈,頭部放大功能

效果圖: 自定義ScrollView: public class MyScrollView extends ScrollView { //----頭部收縮屬性-------- // 記錄首次按下位置 private float mFirstPositi

Android ListView重新整理載入

把這兩天的心得記錄下來,以後用到不會忘。 先說下拉重新整理,下拉重新整理我們主要用到了android自帶v4jar包中的一個控制元件SwipeRefreshLayout,如果下面是xml檔案, 如果你在寫報錯的話,換一個最新的v4包就可以了。 <android

listView重新整理,分頁載入(無更多資料時新增底線)

package com.example.yjyg.mytourismnote.Activity; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.suppo

詳解自主實現RecyclerView重新整理、上載入、Header、Footer以及swiperefreshlayout的部分講解

  年前年後的那段時間比較忙,忙的來忘了寫部落格。最近空閒了,有時間了,來看部落格發現有兩個來月沒有發文章了,對自己的沒有堅持先來幾個,部落格還是要寫的,以後會持續更新。   廢話少說,網上關於列表控

[Android]Ultra-Pull-To-Refresh之listview重新整理、上載入的用例-已更新

前言 本次demo的編輯環境為android studio,java1.8 截圖 使用 1.本示例依賴jar如下: dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) co

android listView重新整理試用所有View

本文實現listView下拉重新整理的功能,並且適配所有view。 /** * Created by zbq on 2015/10/27. * 自定義的佈局,用來管理三個子控制元件,其中一個是下拉頭,一個是包含內容的pullableView(可以是實現Pullable

當PullToRefreshScrollView裡面巢狀ListView,重新整理ListView主動向上滑

當PullToRefreshScrollView裡面巢狀ListView,ListView上面還是有內容的,當下拉重新整理的 時候,資料填充完成之後ListView就會往上面滑動,導致ListView上面的資料沒法顯示,這個時候,我們能夠設定ListView上面的控制元件

SwipeToLoadLayout實現Android重新整理及上載入

最近一段時間需要對手上的專案進行重構,一方面等甲方對業務流程的需求,另一方面由於新配了電腦可以流暢的執行Android Studio了正好也閒著沒事就打算把UI重新搞一搞順便降低各個模組的耦合度,在實現列表的地方噁心到我了。 因為之前一直用的的PullToRefresh在列

jquery mobile實現html5重新整理實現pc與phone的相容

<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <m