android之超級簡單的下拉回彈--仿QQ個人主頁

先看效果:
這裏寫圖片描述
效果不錯吧!

進入主題之前,先了解ImageView的scaleType的center_crop,網絡上說的已經很清楚了 : 以下抄自網絡:

  1. android:scaleType=”centerCrop”
    以填滿整個ImageView爲目的,將原圖的中心對準ImageView的中心,等比例放大原圖,直到填滿ImageView爲止(指的是ImageView的寬和高都要填滿),原圖超過ImageView的部分作裁剪處理。

    均衡的縮放圖像(保持圖像原始比例),使圖片的兩個座標(寬、高)都大於等於 相應的視圖座標(負的內邊距)。圖像則位於視圖的中央。 在XML 中可以使用的語法:android:scaleType=”centerCrop”。

不說廢話,直接進入主題!!

思路

  1. 先將topView的佈局和listview平級,然後將topview以及topview包裹的imageView中傳listview,即一般是activity的layout
  2. 重寫listView的ontoucEvent()方法,但不做任何攔截,只在action時,控制imageView以及topView的高度,使其重新layout然後重新佈局就可以了。
  3. 以上是大概思路,這裏具體分析:當action_down時記錄其初始位置,action_move時得到dy,通過dy來判斷是上啦還是下拉:
    (1)dy>0,則是下拉,不斷重新設置topView和imageView的高度,又因爲imageView的scaleType=center_crop,所以圖片會按照這個規則進行等比拉伸,當到達圖片最大時就會有不斷放大的過程
    當鬆開手或者手指移出屏幕外時(action_up|action_outside|action_cancel)時讓其回到初始位置,並伴着回彈過程,這裏通過自定義動畫讓其具備回彈效果
    (2)dy<0,則是上拉,上推的過程,由於topView和Imageview不具備滾動的效果,所以上推也是通過控制topView和ImageView的高度,並且當TopVIew和ImageView滑出屏幕時就不在更改高度防止不斷的繪製,提高性能。

ok,大體思路就這樣。具體分析代碼如下:

實現:

activity的xml
stretch_act.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:orientation="vertical">

    <!--這是topView-->
    <RelativeLayout
        android:id="@+id/rl_top"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >

        <!--這是imageView,一定要設置scaleType爲centerCrop-->
        <ImageView
            android:id="@+id/iv_stretch_pic"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scaleType="centerCrop"
            android:src="@drawable/stretch_s"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/iv_stretch_pic"
            android:text="你最美,你最酷…………^^"
            android:textSize="16sp"/>

    </RelativeLayout>
<!--這是自定義的listview-->
    <com.example.zwr.myapplication.widget.StretchListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:cacheColorHint="@null"
        android:divider="@null"
        android:listSelector="#00000000"/>


</LinearLayout>

看看StretchListView:

/***
     * @param topView
     * @param imgResId 圖片id
     */
    public void setTopView(View topView, int imgResId) {
        if (null != topView) {
            this.topView = topView;
            imageView = (ImageView) topView.findViewById(imgResId);
        }
    }

通過這個對外的方法,將topView以及ImageView的id傳進來

分析:重新ListView的onTouchEvent():

ACTION_DOWN:
     case MotionEvent.ACTION_DOWN:
                startY = ev.getRawY();
                if (!hadInit) {//初始化,只要初始化一次就夠了
                    childAt0Top = getChildAt(0).getTop();
                    ivInitHeight = imageView.getHeight();
                    hadInit = true;
                }
                break;

只是進行一些初始化操作:

 1. startY:相對於屏幕頂部的高度
 2. childAt0Top,獲取listview的第一個view的top距離、
 3. ivInitHeight:獲取ImageView的初始高度,即剛進來時的高度

ACTION_MOVE:

 case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "dy = " + dy);
                dy = ev.getRawY() - startY;
                if (dy > 0 && 0 == getFirstVisiblePosition() && 
                childAt0Top == getChildAt(0).getTop()) {//(1)手指從上往下拉:下拉
                    int tempDy = (int) (dy + 0.5);
                    //一定也要給topView增加一定的高度,否則從上啦到下拉就不會顯示
                    imageView.getLayoutParams().height = imageView.getHeight() + tempDy;
                    topView.getLayoutParams().height = topView.getHeight() + tempDy;
                    topView.requestLayout();
                    isChangedHeight = true;

                } else {//(2)手指從下往上拉:上拉
                    int tempDy = (int) (dy - 0.5);
                    int currHeight = imageView.getHeight();
                    float translationY = getNegativeMaxValue(tempDy, -currHeight, 0);
                    if (translationY <= 0 && currHeight > 0) {
                        LinearLayout.LayoutParams lp = 
                        (LinearLayout.LayoutParams) topView.getLayoutParams();
                        //一定要減去titleBar,如果沒有去掉Winow.xxx.Title,還要減去這個高度,否則會顯示不全
                        lp.height = topView.getHeight() + (int) translationY;
                        topView.requestLayout();//
                        isChangedHeight = true;
                    }
                }
                //用這個getRawY而不是用getY,是因爲listview也會隨着改變,
                //而getY獲取的就是listview本身的Y,所以基本是變化不大的,
                // 而使用getRawY相對於屏幕的距離,保證滑動了多大的距離就改變多大的距離
                startY = ev.getRawY();
                break;

當下拉時:主要條件如下:

  1. dy > 0 && 0 == getFirstVisiblePosition() && childAt0Top ==
    getChildAt(0).getTop()
    意思是當下拉時,並且listview的第一個位置顯示全了,才能下拉放大圖片,這是避免,listview已經發生滾動了,需要回到初始位置才能下拉放大,否則會出現,立即下拉放大,體驗不好
  2. 當上拉時 主要條件
    if (translationY <= 0 && currHeight > 0)
    currHeight>0:當前ImageView的高度,如果已經滾動到頂部或者超出,則不再進行滾動,防止已經滾出屏幕不可視了,還在進行滾動。
    translationY<=0: 這個值是滾動的距離,這個距離不能超過ImageView的高度,由於上拉時dy是負值,所以要判斷是否小於0;其主要方法如下:
  float translationY = getNegativeMaxValue(tempDy, -currHeight, 0);
   /***
     * 手指上移過程dy是負數
     * 返回負數最大值:0是最大值,不可以超過
     *
     * @param value           移動的最終距離:上次的位置+當次移動的偏移量之和,就是本次要移動的最終的偏移量
     * @param canMoveMaxValue 可移動的最大值
     * @param maxValue
     * @return
     */
    public static float getNegativeMaxValue(float value,float canMoveMaxValue, float maxValue) {
        return Math.min(maxValue, Math.max(canMoveMaxValue, value));
    }

ACTION_UP:

case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (isChangedHeight) {
                    if (imageView.getHeight() > ivInitHeight) {// (1)手指從上往下拉:下拉
                        ResetAnimation resetAnimation = 
                        new ResetAnimation(ivInitHeight, imageView, topView);
                        resetAnimation.setDuration(200);
                        imageView.startAnimation(resetAnimation);
                    } else {//(2)手指從下往上拉:上拉。。。這個不用處理。。。因爲上拉後鬆開讓其topview固定

                    }
                    isChangedHeight = false;
                }
                break;

isChangedHeight:當發生ImageView發生改變,並且是下拉時,這是鬆開手或者手指移出屏幕,則讓其回彈到初始位置;這裏是通過自定義動畫來改變其變化的高度,達到回彈效果 代碼如下

 /**
     * 自定義回彈動畫,使imageView和topView過渡回彈到初始位置
     */
    static class ResetAnimation extends Animation {
        private View topView;
        private int topCurrHeight;

        private ImageView ivStretch;
        private int ivInitHeight;
        private int ivCurrHeight;

        public ResetAnimation(int ivInitHeiht, ImageView ivStretch, View topView) {

            this.ivInitHeight = ivInitHeiht;
            this.ivCurrHeight = ivStretch.getHeight();
            this.topCurrHeight = topView.getHeight();
            this.ivStretch = ivStretch;
            this.topView = topView;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            int dy = (int) ((ivCurrHeight - ivInitHeight) * interpolatedTime);
            Log.d(TAG, "anim dy = " + dy);
            ivStretch.getLayoutParams().height = ivCurrHeight - dy;
            topView.getLayoutParams().height = topCurrHeight - dy;
            topView.requestLayout();
        }
    }

其實主要是applyTransformation(float interpolatedTime, Transformation t) 這個方法
主要是通過這個漸變因子interpolatedTime來控制,其值範圍是(0~1) 所以計算漸變的高度如下
int dy = (int) ((ivCurrHeight - ivInitHeight) * interpolatedTime);
然後一定要記得調用topView.requestLayout(),讓其重新佈局繪製。這樣就完成了,所有代碼,也就一百行代碼左右,是不是很簡單。而且通過這個demo,可以很好的拓展到scrollview中。

注意:
網上有些demo是通過overScrollBy()這個方法中搞事情,因爲

/***
     * 這個方法是在滑出屏幕時回調,但是由於android系統國內廠商修改的面目全非,有些機型是不會回調的,比如vivo
     * 所以不要使用這個方法搞事情
     *
     * @param scrollX
     * @param scrollY
     */
    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, 
    int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, 
    boolean isTouchEvent) {
        Log.d(TAG, "deltax = " + deltaX + " deltaY = " + deltaY);
        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
         scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }
  其自帶dy,還有一些其它的參數,應有盡有。但是由於android系統是開源的導致有些系統是無法回調這個方法的,以至於無法實現回彈效果(比如:vivoX5)等等。所以在onTouchEvent()搞事情纔是王道

ok!,以上有什麼問題,請不吝指正!!!

Demo:http://download.csdn.net/detail/zhongwn/9765268

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章