自定義控件之重寫(xiě)ScrollView實(shí)現(xiàn)圖片下拉放大

前言

因?yàn)楣卷?xiàng)目要實(shí)現(xiàn)一個(gè)效果让腹,在ScrollView沒(méi)有向下滾動(dòng)時(shí)焦读,下拉(未重寫(xiě)前下拉是沒(méi)有任何效果的)放大頂部的圖片仓蛆,當(dāng)時(shí)去網(wǎng)上找了,記得以前見(jiàn)過(guò)很多這樣的控件的蝌诡,現(xiàn)在卻找半天也很難找到一個(gè)徒河,好不容易找到了2個(gè),發(fā)現(xiàn)效果都和需求上面的效果有偏差送漠,最后沒(méi)有辦法只能是自己寫(xiě)了顽照,花費(fèi)了半天時(shí)間研究出來(lái)了,同時(shí)為了記錄實(shí)現(xiàn)思路闽寡,所以就有了此文章

效果

效果圖

實(shí)現(xiàn)思路

攔截ScrollView的觸摸滑動(dòng)事件(ACTION_MOVE),記錄下當(dāng)前事件y軸坐標(biāo)代兵,判斷當(dāng)前ScrollView的Y軸滾動(dòng)進(jìn)度(getScrollY)是否等于0,等于0就與上次事件記錄的位置進(jìn)行對(duì)比爷狈,如果為正數(shù)就放大(X軸是從左往右植影,Y軸是從上往下,所以下拉時(shí)本次事件的Y軸會(huì)大于上次事件的Y軸)涎永,每次事件都通過(guò)設(shè)置ImageView的高度來(lái)放大圖片控件(本來(lái)想用屬性動(dòng)畫(huà)的思币,但是因?yàn)槊總€(gè)事件放大的比例非常小,所以最后就沒(méi)使用羡微,直接通過(guò)修改屬性來(lái)實(shí)現(xiàn))谷饿,同時(shí)記錄從開(kāi)始到現(xiàn)在事件位置一共偏移了多少,當(dāng)偏移量大于最大值的妈倔,就停止放大并將偏移量設(shè)置為最大值博投,當(dāng)偏移量小于0時(shí),則將偏移量設(shè)置為0盯蝴,同時(shí)不再繼續(xù)攔截事件毅哗。注意被放大的圖片需要設(shè)置scaleType為centerCrop听怕,這樣當(dāng)圖片高度發(fā)生變化時(shí),圖片內(nèi)容才會(huì)跟著大虑绵,當(dāng)然其他幾種模式有些模式也能跟著放大尿瞭,但是具體可以自己去測(cè)試,我就不去測(cè)試了翅睛,畢竟我已經(jīng)達(dá)到我要的效果了

好了声搁,廢話少說(shuō),先貼代碼宏所,再對(duì)代碼進(jìn)行說(shuō)明

代碼

package wang.raye.library;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;


/**
 * 重寫(xiě)讓ScrollView有滾動(dòng)監(jiān)聽(tīng)(23以前是沒(méi)有滾動(dòng)監(jiān)聽(tīng)的)
 * 攔截touch事件酥艳,讓其支持下拉放大圖片
 * Created by Raye on 2016/6/11.
 */
public class ZoomScrollView extends ScrollView {

    private View zoomView;
    /** 記錄上次事件的Y軸*/
    private float mLastMotionY;
    /** 記錄一個(gè)滾動(dòng)了多少距離摊溶,通過(guò)這個(gè)來(lái)設(shè)置縮放*/
    private int allScroll = -1;
    /** 控件原本的高度*/
    private int height = 0;
    /** 被放大的控件id*/
    private int zoomId;
    /** 最大放大多少像素*/
    private int maxZoom;
    /** 滾動(dòng)監(jiān)聽(tīng)*/
    private ScrollViewListener scrollViewListener = null;
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            allScroll -= 25;
            if(allScroll < 0){
                allScroll = 0;
            }
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
            lp.height = (int) (height + allScroll/2);
            zoomView.setLayoutParams(lp);
            if(allScroll != 0){
                handler.sendEmptyMessageDelayed(1,10);
            }else{
                allScroll = -1;
            }
        }
    };
    public ZoomScrollView(Context context) {
        super(context);
    }

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

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

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public ZoomScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        zoomView = findViewById(zoomId);
    }

    private void init(AttributeSet attrs){
        TypedArray t = getContext().obtainStyledAttributes(attrs, R.styleable.ObservableScrollView);
        zoomId = t.getResourceId(R.styleable.ObservableScrollView_zoomId,-1);
        maxZoom = t.getDimensionPixelOffset(R.styleable.ObservableScrollView_maxZoom,0);
    }

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if(zoomView == null || maxZoom == 0){
            return super.dispatchTouchEvent(event);
        }

        final int action = event.getAction();

        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            if(allScroll != -1){
                handler.sendEmptyMessageDelayed(1,10);
            }
            return super.dispatchTouchEvent(event);
        }

        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                final float y = event.getY();
                final float diff, absDiff;
                diff = y - mLastMotionY;
                mLastMotionY = y;
                absDiff = Math.abs(diff);
                if(allScroll >= 0 && absDiff > 1){
                    allScroll += diff;

                    if(allScroll < 0){
                        allScroll = 0;
                    }else if(allScroll > maxZoom){
                        allScroll = maxZoom;
                    }
                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
                    lp.height = (int) (height + allScroll/2);
                    zoomView.setLayoutParams(lp);
                    if(allScroll == 0){
                        allScroll = -1;
                    }
                    return false;
                }
                if (isReadyForPullStart()) {
                    if (absDiff > 0 ) {
                        if (diff >= 1f && isReadyForPullStart()) {
                            mLastMotionY = y;
                            allScroll = 0;
                            height = zoomView.getHeight();
                            return true;
                        }
                    }
                }
                break;
            }


        }

        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(allScroll != -1){
            Log.i("ScrollView","onTouchEvent");
            return false;
        }
        return super.onTouchEvent(ev);
    }



    /**
     * 返回是否可以開(kāi)始放大
     * @return
     */
    protected boolean isReadyForPullStart() {
        return getScrollY() == 0;
    }


    @Override
    protected void onScrollChanged(int x, int y, int oldx, int oldy) {
        super.onScrollChanged(x, y, oldx, oldy);
        if (scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
        }
    }
    public interface ScrollViewListener {

        void onScrollChanged(ZoomScrollView scrollView, int x, int y, int oldx, int oldy);

    }
}

重要點(diǎn)爬骤,從上往下

private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            allScroll -= 25;
            if(allScroll < 0){
                allScroll = 0;
            }
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
            lp.height = (int) (height + allScroll/2);
            zoomView.setLayoutParams(lp);
            if(allScroll != 0){
                handler.sendEmptyMessageDelayed(1,10);
            }else{
                allScroll = -1;
            }
        }
    };

這里是當(dāng)ACTION_UP事件發(fā)生時(shí),如果圖片還在放大狀態(tài)莫换,就模擬動(dòng)畫(huà)效果霞玄,吧圖片縮放回去,當(dāng)然是可以用屬性動(dòng)畫(huà)的拉岁,只是我之前沒(méi)用屬性動(dòng)畫(huà)坷剧,所以這里也直接用這個(gè)了

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        zoomView = findViewById(zoomId);
    }

這里是當(dāng)控件從xml中初始化完成的生命周期方法,在這里我們找到被放大的圖片控件

private void init(AttributeSet attrs){
        TypedArray t = getContext().obtainStyledAttributes(attrs, R.styleable.ObservableScrollView);
        zoomId = t.getResourceId(R.styleable.ObservableScrollView_zoomId,-1);
        maxZoom = t.getDimensionPixelOffset(R.styleable.ObservableScrollView_maxZoom,0);
    }

這段代碼相信很容易看懂喊暖,就是獲取2個(gè)自定義屬性惫企,一個(gè)是被放大的圖片控件id,一個(gè)是最大的放大像素

最主要的地方陵叽,事件攔截
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if(zoomView == null || maxZoom == 0){
            return super.dispatchTouchEvent(event);
        }

        final int action = event.getAction();

        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            if(allScroll != -1){
                handler.sendEmptyMessageDelayed(1,10);
            }
            return super.dispatchTouchEvent(event);
        }

        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                final float y = event.getY();
                final float diff, absDiff;
                diff = y - mLastMotionY;
                mLastMotionY = y;
                absDiff = Math.abs(diff);
                if(allScroll >= 0 && absDiff > 1){
                    allScroll += diff;

                    if(allScroll < 0){
                        allScroll = 0;
                    }else if(allScroll > maxZoom){
                        allScroll = maxZoom;
                    }
                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
                    lp.height = (int) (height + allScroll/2);
                    zoomView.setLayoutParams(lp);
                    if(allScroll == 0){
                        allScroll = -1;
                    }
                    return false;
                }
                if (isReadyForPullStart()) {
                    if (absDiff > 0 ) {
                        if (diff >= 1f && isReadyForPullStart()) {
                            mLastMotionY = y;
                            allScroll = 0;
                            height = zoomView.getHeight();
                            return true;
                        }
                    }
                }
                break;
            }


        }

        return super.dispatchTouchEvent(event);
    }
詳細(xì)說(shuō)明
if(zoomView == null || maxZoom == 0){
       return super.dispatchTouchEvent(event);
}

當(dāng)控件為空和最大放大像素為0 的時(shí)候狞尔,不進(jìn)行事件攔截

        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            if(allScroll != -1){
                handler.sendEmptyMessageDelayed(1,10);
            }
            return super.dispatchTouchEvent(event);
        }

當(dāng)事件取消和手指松開(kāi)時(shí),判斷當(dāng)前偏移量(allScroll )是否回到了最初狀態(tài)-1巩掺,如果沒(méi)有說(shuō)明圖片沒(méi)有縮放偏序,要縮放回去

            case MotionEvent.ACTION_MOVE: {
                final float y = event.getY();
                final float diff, oppositeDiff, absDiff;
                diff = y - mLastMotionY;
                mLastMotionY = y;
                absDiff = Math.abs(diff);
                if( allScroll >= 0 && absDiff > 1){
                    allScroll += diff;

                    if(allScroll < 0){
                        allScroll = 0;
                    }else if(allScroll > maxZoom){
                        allScroll = maxZoom;
                    }
                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) zoomView.getLayoutParams();
                    lp.height = (int) (height + allScroll/2);
                    zoomView.setLayoutParams(lp);
                    if(allScroll == 0){
                        allScroll = -1;
                    }
                    return false;
                }
                if (isReadyForPullStart()) {
                    if (absDiff > 0 ) {
                        if (diff >= 1f && isReadyForPullStart()) {
                            mLastMotionY = y;
                            allScroll = 0;
                            height = zoomView.getHeight();
                            return true;
                        }
                    }
                }
                break;
            }

攔截移動(dòng)事件,每次記錄下Y軸坐標(biāo)胖替,當(dāng)滾動(dòng)為0的時(shí)候研儒,就計(jì)算與上次坐標(biāo)的偏移量,大于0就開(kāi)始放大独令,每次放大總偏移值的二分之一端朵,因?yàn)槊看畏糯罂偲浦档男Ч淮蠛每矗瑫r(shí)判斷總偏移值是否大于最大偏移值燃箭,大于就設(shè)置總偏移值為最大值逸月,相當(dāng)于停止放大。如果小于0遍膜,就把總偏移值設(shè)置為0碗硬,并且重置偏移值的為-1瓤湘,-1的時(shí)候,就不會(huì)攔截事件

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(allScroll != -1){
            Log.i("ScrollView","onTouchEvent");
            return false;
        }
        return super.onTouchEvent(ev);
    }

重寫(xiě)onTouchEvent恩尾,當(dāng)偏移值不是-1的時(shí)候弛说,說(shuō)明圖片在進(jìn)行放大或縮放,這時(shí)候不能讓ScrollView滾動(dòng)翰意,所以需要把onTouchEvent攔截掉

    protected boolean isReadyForPullStart() {
        return getScrollY() == 0;
    }

獲取當(dāng)前ScrollView的滾動(dòng)位置木人,是0的時(shí)候才可以開(kāi)始放大圖片

最后說(shuō)兩句

控件中還有個(gè)監(jiān)聽(tīng),那個(gè)不用管冀偶,那個(gè)是為了獲取滾動(dòng)位置來(lái)設(shè)置標(biāo)題欄透明度的醒第,跟本文內(nèi)容無(wú)關(guān),所以就不詳細(xì)說(shuō)明了进鸠。當(dāng)然這個(gè)自定義控件只是為了實(shí)現(xiàn)我項(xiàng)目中需求的效果稠曼,很簡(jiǎn)陋,實(shí)現(xiàn)方法也很簡(jiǎn)單客年,所以歡迎高手前來(lái)指點(diǎn)霞幅。需要效果圖demo的請(qǐng)點(diǎn)擊demo github地址,另外同時(shí)也歡迎大家吐槽交流(QQ群:123965382)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市量瓜,隨后出現(xiàn)的幾起案子司恳,更是在濱河造成了極大的恐慌,老刑警劉巖绍傲,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扔傅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡烫饼,警方通過(guò)查閱死者的電腦和手機(jī)猎塞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)枫弟,“玉大人邢享,你說(shuō)我怎么就攤上這事〉” “怎么了骇塘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)韩容。 經(jīng)常有香客問(wèn)我款违,道長(zhǎng),這世上最難降的妖魔是什么群凶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任插爹,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赠尾。我一直安慰自己力穗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布气嫁。 她就那樣靜靜地躺著当窗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寸宵。 梳的紋絲不亂的頭發(fā)上崖面,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音梯影,去河邊找鬼巫员。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甲棍,可吹牛的內(nèi)容都是我干的简识。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼救军,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼财异!你這毒婦竟也來(lái)了倘零?” 一聲冷哼從身側(cè)響起唱遭,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呈驶,沒(méi)想到半個(gè)月后拷泽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袖瞻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年司致,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聋迎。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脂矫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霉晕,到底是詐尸還是另有隱情庭再,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布牺堰,位于F島的核電站拄轻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏伟葫。R本人自食惡果不足惜恨搓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斧抱,春花似錦常拓、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至盏浙,卻和暖如春眉睹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背废膘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工竹海, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丐黄。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓斋配,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親灌闺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嗡午,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 一、Android開(kāi)發(fā)初體驗(yàn) 二桩砰、Android與MVC設(shè)計(jì)模式模型對(duì)象存儲(chǔ)著應(yīng)用的數(shù)據(jù)和業(yè)務(wù)邏輯倦卖。模型類(lèi)通常用來(lái)...
    為夢(mèng)想戰(zhàn)斗閱讀 886評(píng)論 0 3
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,098評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件蕉斜、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,098評(píng)論 4 62
  • 1 洞庭湖畔逾柿,漁歌唱晚。正值初春宅此,微風(fēng)習(xí)習(xí)机错,夕陽(yáng)下的湖水更顯靜謐悠然。 這時(shí)父腕,一輛馬車(chē)緩緩駛來(lái)弱匪,車(chē)中一個(gè)兩鬢斑白的...
    凝流閱讀 941評(píng)論 19 36
  • 認(rèn)知革命與物種滅絕 人類(lèi)在認(rèn)知革命發(fā)生前,掌握了工具和火的使用璧亮,在極短的時(shí)間內(nèi)走上了食物鏈的頂端萧诫,別的物種都沒(méi)來(lái)得...
    大羊愛(ài)生活閱讀 192評(píng)論 0 0