美文网首页Android自定义控件自定义view
自定义控件之重写ScrollView实现图片下拉放大

自定义控件之重写ScrollView实现图片下拉放大

作者: Raye | 来源:发表于2016-06-14 10:34 被阅读1406次

    前言

    因为公司项目要实现一个效果,在ScrollView没有向下滚动时,下拉(未重写前下拉是没有任何效果的)放大顶部的图片,当时去网上找了,记得以前见过很多这样的控件的,现在却找半天也很难找到一个,好不容易找到了2个,发现效果都和需求上面的效果有偏差,最后没有办法只能是自己写了,花费了半天时间研究出来了,同时为了记录实现思路,所以就有了此文章

    效果

    效果图

    实现思路

    拦截ScrollView的触摸滑动事件(ACTION_MOVE),记录下当前事件y轴坐标,判断当前ScrollView的Y轴滚动进度(getScrollY)是否等于0,等于0就与上次事件记录的位置进行对比,如果为正数就放大(X轴是从左往右,Y轴是从上往下,所以下拉时本次事件的Y轴会大于上次事件的Y轴),每次事件都通过设置ImageView的高度来放大图片控件(本来想用属性动画的,但是因为每个事件放大的比例非常小,所以最后就没使用,直接通过修改属性来实现),同时记录从开始到现在事件位置一共偏移了多少,当偏移量大于最大值的,就停止放大并将偏移量设置为最大值,当偏移量小于0时,则将偏移量设置为0,同时不再继续拦截事件。注意被放大的图片需要设置scaleType为centerCrop,这样当图片高度发生变化时,图片内容才会跟着大,当然其他几种模式有些模式也能跟着放大,但是具体可以自己去测试,我就不去测试了,毕竟我已经达到我要的效果了

    好了,废话少说,先贴代码,再对代码进行说明

    代码

    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;
    
    
    /**
     * 重写让ScrollView有滚动监听(23以前是没有滚动监听的)
     * 拦截touch事件,让其支持下拉放大图片
     * Created by Raye on 2016/6/11.
     */
    public class ZoomScrollView extends ScrollView {
    
        private View zoomView;
        /** 记录上次事件的Y轴*/
        private float mLastMotionY;
        /** 记录一个滚动了多少距离,通过这个来设置缩放*/
        private int allScroll = -1;
        /** 控件原本的高度*/
        private int height = 0;
        /** 被放大的控件id*/
        private int zoomId;
        /** 最大放大多少像素*/
        private int maxZoom;
        /** 滚动监听*/
        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);
        }
    
    
    
        /**
         * 返回是否可以开始放大
         * @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);
    
        }
    }
    
    

    重要点,从上往下

    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;
                }
            }
        };
    

    这里是当ACTION_UP事件发生时,如果图片还在放大状态,就模拟动画效果,吧图片缩放回去,当然是可以用属性动画的,只是我之前没用属性动画,所以这里也直接用这个了

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

    这里是当控件从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个自定义属性,一个是被放大的图片控件id,一个是最大的放大像素

    最主要的地方,事件拦截
        @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);
        }
    
    详细说明
    if(zoomView == null || maxZoom == 0){
           return super.dispatchTouchEvent(event);
    }
    

    当控件为空和最大放大像素为0 的时候,不进行事件拦截

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

    当事件取消和手指松开时,判断当前偏移量(allScroll )是否回到了最初状态-1,如果没有说明图片没有缩放,要缩放回去

                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;
                }
    

    拦截移动事件,每次记录下Y轴坐标,当滚动为0的时候,就计算与上次坐标的偏移量,大于0就开始放大,每次放大总偏移值的二分之一,因为每次放大总偏移值的效果不大好看,同时判断总偏移值是否大于最大偏移值,大于就设置总偏移值为最大值,相当于停止放大。如果小于0,就把总偏移值设置为0,并且重置偏移值的为-1,-1的时候,就不会拦截事件

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

    重写onTouchEvent,当偏移值不是-1的时候,说明图片在进行放大或缩放,这时候不能让ScrollView滚动,所以需要把onTouchEvent拦截掉

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

    获取当前ScrollView的滚动位置,是0的时候才可以开始放大图片

    最后说两句

    控件中还有个监听,那个不用管,那个是为了获取滚动位置来设置标题栏透明度的,跟本文内容无关,所以就不详细说明了。当然这个自定义控件只是为了实现我项目中需求的效果,很简陋,实现方法也很简单,所以欢迎高手前来指点。需要效果图demo的请点击demo github地址,另外同时也欢迎大家吐槽交流(QQ群:123965382)

    相关文章

      网友评论

      • 李铭淋:手动点个赞
      • 关山明月:ZoomScrollView 这个自定义ScrollView的里面只能使用LinearLayout么?我使用RelativeLayout就不行了,图片也能放大,但除了放大图片,其他的控件下拉时都不动了
      • 凌云秣:isReadyForPullStart()这个方法某些时候会出错。一直下拉不放。会重新设置height。这样会出现图片越拉越大。这样写:
        protected boolean isReadyForPullStart() {
        return getScrollY() == 0&&allScroll<maxZoom;
        }
        可以避免。
      • 王小小胖:看了你的github mainactivity里也没有怎样对图片放大的?
        Raye:只是把控件放大实现的放大图片而已
      • 捡淑:马克
      • 权仔:写的不错

      本文标题:自定义控件之重写ScrollView实现图片下拉放大

      本文链接:https://www.haomeiwen.com/subject/ppcudttx.html