PullToZoomScroollView

第一次自己寫自定義控件,記錄下一些不懂的知識點。

實現一個帶頭部的ScrollView 下拉頭部放大,上滑有時差效果

下面是上代碼:

public class PullToZoomScrollView extends ScrollView{
    private  boolean isonce;//加載該View的布局時是否是第一次加載,是第一次就讓其實現OnMeasure里的代碼

    private LinearLayout mParentView;//布局的父布局,ScrollView內部只能有一個根ViewGroup,就是此View
    private ViewGroup mTopView;//這個是帶背景的上半部分的View,下半部分的View用不到的

    private int mScreenHeight;//整個手機屏幕的高度,這是為了初始化該View時設置mTopView用的
    private int mTopViewHeight;//這個就是mTopView的高度

    private int mCurrentOffset=0;//當前右側滾條頂點的偏移量。ScrollView右側是有滾動條的,當下拉時,
    //滾動條向上滑,當向下滑動時,滾動條向下滑動。

    private ObjectAnimator oa;//這個是對象動畫,這個在本View里很簡單,也很獨立,就在這里申明一下,后面有兩個方法
    //兩個方法是:setT(int t),reset()兩個方法用到,其他都和它無關了。

    /**
     * 初始化獲取高度值,并記錄
     * @param context
     * @param attrs
     */
    public PullToZoomScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setOverScrollMode(View.OVER_SCROLL_NEVER);
        WindowManager wm= (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics metrics=new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metrics); //獲取描述該顯示的大小和密度的顯示指標
        mScreenHeight=metrics.heightPixels;  //用顯示器的高度
        mTopViewHeight=mScreenHeight/2-(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90, context.getResources().getDisplayMetrics());
    }

    /**
     * 將記錄的值設置到控件上,并只讓控件設置一次
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(!isonce) {
            mParentView = (LinearLayout) this.getChildAt(0);
            mTopView = (ViewGroup) mParentView.getChildAt(0);
            mTopViewHeight = mTopView.getLayoutParams().height;
            mTopView.getLayoutParams().height = mTopViewHeight;
            isonce=true;
        }
    }

    private float startY=0;//向下拉動要放大,手指向下滑時,點擊的第一個點的Y坐標
    private boolean isBig;//是否正在向下拉放大上半部分View
    private boolean isTouchOne;//是否是一次連續的MOVE,默認為false,
    //在MoVe時,如果發現滑動標簽位移量為0,則獲取此時的Y坐標,作為起始坐標,然后置為true,為了在連續的Move中只獲取一次起始坐標
    //當Up彈起時,一次觸摸移動完成,將isTouchOne置為false
    private float distance=0;//向下滑動到釋放的高度差
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action =ev.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                if(mCurrentOffset<=0){
                    if(!isTouchOne){
                        startY=ev.getY();
                        isTouchOne=true;
                    }
                    distance=ev.getY()-startY;
                    if(distance>0){
                        isBig=true;
                        setT((int)-distance/4);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if(isBig) {
                    reset();
                    isBig=false;
                }
                isTouchOne=false;
            break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 對象動畫要有的設置方法
     * @param t
     */
    public void setT(int t) {
        scrollTo(0, 0);
        if (t < 0) {
            mTopView.getLayoutParams().height = mTopViewHeight-t;
            mTopView.requestLayout();
        }
    }

    /**
     * 主要用于釋放手指后的回彈效果
     */
    private void reset() {
        if (oa != null && oa.isRunning()) {
            return;
        }
        oa = ObjectAnimator.ofInt(this, "t", (int)-distance / 4, 0);
        oa.setDuration(150);
        oa.start();
    }

    /**
     * 這個是設置向上滑動時,上半部分View滑動速度讓其小于下半部分
     * @param l
     * @param t
     * @param oldl
     * @param oldt
     */
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        mCurrentOffset = t;//右邊滑動標簽相對于頂端的偏移量
        //當手勢上滑,則右側滾動條下滑,下滑的高度小于TopView的高度,則讓TopView的上滑速度小于DownView的上滑速度
        //DownView的上滑速度是滾動條的速度,也就是滾動的距離是右側滾動條的距離
        //則TopView的速度要小,只需要將右側滾動條的偏移量也就是t縮小一定倍數就行了。我這里除以2速度減小1倍
        if (t <= mTopViewHeight&&t>=0&&!isBig) {
            mTopView.setTranslationY(t / 2);//使得TopView滑動的速度小于滾輪滾動的速度
        }
        if(isBig){
            scrollTo(0,0);
        }

    }
}

下面記錄流程:
1.創建PullToZoomScrollView 繼承 scrollView
這里只提供了一個構造方法必須在在xml文件中創建。
構造方法中做了幾件事:設置ScrollView模式 this.setOverScrollMode(View.OVER_SCROLL_NEVER); 去除下拉時陰影
計算TopView的高度:獲取WindowManager 從windowManager中拿到 metrics 屏幕數據參數,

TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90, context.getResources().getDisplayMetrics());

這里解釋下這個函數:將包含維度的解壓縮復合數據值轉換為其最終浮點值。
三個參數分別為:
unit:轉換單位。
value: 應用單位的價值。
metrics:在轉換中使用的當前顯示指標 - 提供顯示密度和縮放信息。

2.onMeasure()中,當第一次執行時,拿到mParentView 、mTopView 、并設置mTopViewHeight.

  1. onTouchEvent(MotionEvent ev)
    MotionEvent.ACTION_MOVE,在頂部且下滑時,記錄下第一次滑動的點startY
    通過之后的觸摸點計算出滑動距離distance,并設置mTopView的高度。
    這里記錄下 requestLayout 與 invalidate的區別:

requestLayout:當view確定自身已經不再適合現有的區域時,該view本身調用這個方法要求parent view重新調用他的onMeasure onLayout來對重新設置自己位置。
特別的當view的layoutparameter發生改變,并且它的值還沒能應用到view上,這時候適合調用這個方法。

invalidate:該方法的調用會引起View樹的重繪,常用于內部調用(比如 setVisiblity())或者需要刷新界面的時候,需要在主線程(即UI線程)中調用該方法。一般只會調用onDraw重新繪制。

MotionEvent.ACTION_UP,在滑動結束后如果動畫還未結束則調用reset()方法
執行動畫

  oa = ObjectAnimator.ofInt(this, "t", (int)-distance / 4, 0);

4.onScrollChanged
在滑動時記錄偏移量 mCurrentOffset
這里產生頭部時差效果 將mTopView的滑動速度小于滾輪滑動的速度

   if (t <= mTopViewHeight&&t>=0&&!isBig) {
            mTopView.setTranslationY(t / 2);//使得TopView滑動的速度小于滾輪滾動的速度
        }

如果下拉放大時,執行scrollTo(0,0).

第一次寫技術文章,寫的不好見諒

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,737評論 25 708
  • 本文分析版本: Android API 23 1.簡介 ScrollView是我們在開發中經常使用的控件。當我們需...
    SkyKai閱讀 8,977評論 3 54
  • 3.4 View詳解 3.10.1 概述 View系統定義了從用戶輸入消息到消息處理的全過程:用戶通過觸摸屏或者鍵...
    jianhuih閱讀 618評論 0 0
  • 2017.11.29號 星期 三 天氣 晴轉陰 今天晚上接著孩子后我們一起去超市買了些東西去媽媽家吃火鍋,...
    呂政民閱讀 143評論 0 0
  • 01。 三年前的夏天,我和女朋友在省醫院后面的老校區里租了套二的房間,一來上班更近了,二來房租便宜。 小區和送仙橋...
    大山里的阿木閱讀 828評論 1 8