Android 自定义纵向滚动调,自定义垂直滚动条,自定义Webview滚动条

背景:项目中有一个需求说要做自定义滚动条,而且要求滚动条在webview的左边,一看UI设计稿就蒙了,于是网上找了一堆结果发现没几个有用的要么就是要用积分换,泱泱大国内卷至此无语的很,还去Android bus上找了下,结果发现大背景下啥都不行。于是一怒之下自己写了一个!!!

Android 自定义纵向滚动调,自定义垂直滚动条,自定义Webview滚动条_第1张图片

废话不多说直接上效果图

原理:继承View自己写了一个控件

核心功能:

1. 传统滚动条有的功能他都有,可进行手势滑动,可与webView进行滑动联动

2. 可以控制滚动条在任意位置,和任意高度,不用与webview高度保持一致

3.可以控制滚动条宽度提高可触摸性,甩传统滚动条不好触摸滚动缺点一条街

4.暴露了接口可以进行到顶部到底部的相关逻辑处理

5.可以实现翻页功能,甩传统滚动条一条街

开发碰到的问题:

1. 滑动面太小,导致触摸范围太小

2.头部判断、底部判断不准确

3.指示器滑动范围不限制导致的一系列问题

4.webview的页面大小获取有误导致的问题

5.onDraw绘制逻辑,如果让轨迹和指示器只绘制居中等

好了开始最重要代码review环节

Android 自定义纵向滚动调,自定义垂直滚动条,自定义Webview滚动条_第2张图片

第一版代码:滚动条背景写死、指示器背景写死,宽度大小写死

package com.sample;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;

public class CustomScrollBar extends View {
    private final static String TAG = "CustomScrollBar";
    private Paint paint;
    private float scrollPosition;
    private WebView webView;

    private float trackWidth = 5; // 轨迹的宽度

    private int barWidth = 16;

    private int barHeight;

    private Context mContext;

    public CustomScrollBar(Context context) {
        super(context);
        this.mContext = context;
        init();
    }

    public CustomScrollBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        init();
    }

    public CustomScrollBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setColor(Color.GRAY);
        paint.setStyle(Paint.Style.FILL);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        barHeight = height / 3;

        float left = (width - barWidth)/2;
        float top = scrollPosition * (height - barHeight);
        float right = width / 2  ;
        float bottom = top + barHeight;

        Log.e(TAG, "left = " + left + "top = " + top + "  right = " + right + "  bottom = " + bottom);

        // 绘制轨迹
        paint.setColor( Color.parseColor("#2B2E31"));
        paint.setStrokeWidth(trackWidth);
        float trackStartX = left + barWidth/4;
        float trackEndX = left + barWidth /4;
        float trackStartY = 0;
        float trackEndY = height;
        // 绘制轨迹
        canvas.drawLine(trackStartX, trackStartY, trackEndX, trackEndY, paint);


        // 绘制bar
        paint.setColor(Color.parseColor("#8B8B8B"));
        paint.setStrokeWidth(barWidth);
        top = Math.max(Math.min(top, height - barHeight), 0);
        bottom = Math.max(Math.min(bottom, height), barHeight);
        canvas.drawRect(left, top < 0 ? 0 : top, right, bottom, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                float y = event.getY() / getHeight();
                scrollToPosition(y);
                return true;
            default:
                return super.onTouchEvent(event);
        }
    }

    private void scrollToPosition(float position) {
        if (webView != null) {
            int contentHeight = (int) (webView.getContentHeight() * webView.getScale());
            int webViewHeight = webView.getHeight();
            int scrollRange = contentHeight - webViewHeight;
            if (scrollRange > 0) {
                int scrollTo = (int) (position * scrollRange);
                Log.i(TAG,"scrollToPosition  position = " + position + "  scrollTo = " + scrollTo);
                webView.scrollTo(0, Math.min(Math.max(scrollTo, 0), scrollRange));
            }
        }
    }

    private void updateScrollBarPosition() {
        if (webView != null) {
            // 获取 WebView 的滚动信息
            int contentHeight = (int) (webView.getContentHeight() * webView.getScale());
            int webViewHeight = webView.getHeight();
            int scrollRange = contentHeight - webViewHeight;
            // 计算滚动条位置
            if (scrollRange > 0) {
                scrollPosition = (float) webView.getScrollY() / scrollRange;
                Log.i(TAG,"updateScrollBarPosition  scrollPosition = " + scrollPosition );
                invalidate();
            }
        }
    }

    public void setWebView(WebView webView) {
        this.webView = webView;

        // 设置 WebView 的滚动监听
        if (webView != null) {
            webView.setOnScrollChangeListener(new OnScrollChangeListener() {
                @Override
                public void onScrollChange(View view, int i, int i1, int i2, int i3) {
                    // 更新滚动条位置
                    updateScrollBarPosition();
                    if (onWebViewScrollChangedCallback != null) {
                        if (webView.getScrollY() == 0) {
                            onWebViewScrollChangedCallback.onScrollStatus(true, false);
                        } else if ( (int) (webView.getContentHeight() * webView.getScale() - webView.getHeight()) - webView.getScrollY() <=1) {
                            onWebViewScrollChangedCallback.onScrollStatus(false, true);
                        } else {
                            onWebViewScrollChangedCallback.onScrollStatus(false, false);
                        }
                    }
                }
            });
        }
    }

    OnWebViewScrollChangedCallback onWebViewScrollChangedCallback;

    public void setOnWebViewScrollChangedCallback(OnWebViewScrollChangedCallback callback) {
        this.onWebViewScrollChangedCallback = callback;
    }

    // Interface for WebView scroll callback
    public interface OnWebViewScrollChangedCallback {
        void onScrollStatus(boolean  frist, boolean last);
    }
}


第二版代码修复上面说到的四个主要问题还增加了xml属性:

package com.sample;



import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;


public class CustomScrollBarNew extends View {
    private final static String TAG = "CustomScrollBar";
    private Paint paint;
    private float scrollPosition;
    private WebView webView;
    private float trackWidth = 2f; // 轨迹的宽度
    private float barWidth = 6f; //指示器宽度
    private float barHeight;
    private int trackColor = Color.parseColor("#2B2E31");
    private int barColor = Color.parseColor("#8B8B8B");


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

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

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

    private void init(Context context) {
        paint = new Paint();
        paint.setColor(Color.GRAY);
        paint.setStyle(Paint.Style.FILL);
    }

    private void init(AttributeSet attrs,Context context){
        // 获取自定义属性值
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomScrollBar);
        trackColor = typedArray.getColor(R.styleable.CustomScrollBar_trackColor, Color.parseColor("#2B2E31"));
        barColor = typedArray.getColor(R.styleable.CustomScrollBar_barColor, Color.parseColor("#8B8B8B"));
        trackWidth = typedArray.getDimension(R.styleable.CustomScrollBar_trackWidth, 2f);
        barWidth = typedArray.getDimension(R.styleable.CustomScrollBar_barWidth, 8f);
        typedArray.recycle(); // 记得回收
        init(context);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        barHeight = getHeight() / 4;
        float left = (width - barWidth) / 2;
        float top = scrollPosition * (height - barHeight);
        float right = width / 2;
        float bottom = top + barHeight;

        // 绘制轨迹
        paint.setColor(trackColor );
        paint.setStrokeWidth(trackWidth);
        float trackStartX = left + barWidth / 4;
        float trackEndX = left + barWidth / 4;
        float trackStartY = 0;
        float trackEndY = height;
        // 绘制轨迹
        canvas.drawLine(trackStartX, trackStartY, trackEndX, trackEndY, paint);

        Log.e(TAG, "left = " + left + "top =" + top + "  right" + right + "  bottom" + bottom);
        // 绘制bar
        paint.setColor(barColor);
        paint.setStrokeWidth(barWidth);
        top = Math.max(Math.min(top, height - barHeight), 0);
        bottom = Math.max(Math.min(bottom, height), barHeight);
        canvas.drawRect(left, top, right, bottom, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                float y = event.getY() / getHeight();
                scrollToPosition(y);
                return true;
            default:
                return super.onTouchEvent(event);
        }
    }

    private void scrollToPosition(float position) {
        if (webView != null) {
            int contentHeight = (int) (webView.getContentHeight() * webView.getScale());
            int webViewHeight = webView.getHeight();
            int scrollRange = contentHeight - webViewHeight;

            if (scrollRange > 0) {
                int scrollTo = (int) (position * scrollRange);
                Log.e(TAG, "scrollToPosition scrollTo : " + scrollTo);
                webView.scrollTo(0, Math.min(Math.max(scrollTo, 0), scrollRange));
            }
        }
    }
    private void updateScrollBarPosition() {
        if (webView != null) {
            // 获取 WebView 的滚动信息
            int contentHeight = (int) (webView.getContentHeight() * webView.getScale());
            int webViewHeight = webView.getHeight();
            int scrollRange = contentHeight - webViewHeight;

            // 计算滚动条位置
            if (scrollRange > 0) {
                scrollPosition = (float) webView.getScrollY() / scrollRange;
                Log.e(TAG, "updateScrollBarPosition scrollPosition : " + scrollPosition);
                invalidate();
            }
        }
    }

    public void setWebView(WebView webView) {
        this.webView = webView;
        // 设置 WebView 的滚动监听
        if (webView != null) {
            webView.setOnScrollChangeListener(new OnScrollChangeListener() {
                @Override
                public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    Log.i(TAG," scrollX = " + scrollX + " scrollY = " + scrollY +" oldScrollX = " + oldScrollX + " oldScrollY = " + oldScrollY);
                    Log.i(TAG," canScrollVertically = " +webView.canScrollVertically(1));
                    // 更新滚动条位置
                    // 更新滚动条位置
                    updateScrollBarPosition();
                    if (onWebViewScrollChangedCallback != null) {
                        if (!webView.canScrollVertically(-1)) {
                            Log.e(TAG, "webView.  头部");
                            onWebViewScrollChangedCallback.onScrollStatus(true, false);
                        } else if (!webView.canScrollVertically(1)) {
                            onWebViewScrollChangedCallback.onScrollStatus(false, true);
                            Log.e(TAG, "webView.  底部");
                        } else {
                            Log.e(TAG, "webView.  中间");
                            onWebViewScrollChangedCallback.onScrollStatus(false, false);
                        }
                    }
                }
            });
        }
    }

    private OnWebViewScrollChangedCallback onWebViewScrollChangedCallback;

    public void setOnWebViewScrollChangedCallback(OnWebViewScrollChangedCallback callback) {
        this.onWebViewScrollChangedCallback = callback;
    }

    // Interface for WebView scroll callback
    public interface OnWebViewScrollChangedCallback {
        void onScrollStatus(boolean  frist, boolean last);
    }
}




主要核心代码:

解决问题一:滑动面过小,暴力解决直接view的width设大点,android:layout_width="100dp"
解决问题二:2.头部判断、底部判断不准确

//是否到底部下面为true时

!webView.canScrollVertically(1)

//判断是否在头部

!webView.canScrollVertically(-1)
解决问题三:指示器滑动范围不限制导致的一系列问题,限定最小值未0,和最大值scrollRange
private void scrollToPosition(float position) {
    if (webView != null) {
        int contentHeight = (int) (webView.getContentHeight() * webView.getScale());
        int webViewHeight = webView.getHeight();
        int scrollRange = contentHeight - webViewHeight;

        if (scrollRange > 0) {
            int scrollTo = (int) (position * scrollRange);
            Log.e(TAG, "scrollToPosition scrollTo : " + scrollTo);
            webView.scrollTo(0, Math.min(Math.max(scrollTo, 0), scrollRange));
        }
    }
}
解决问题四:webview的页面大小获取有误导致的问题,注意一定要乘以webView.getScale())
(int) (webView.getContentHeight() * webView.getScale());

项目链接

https://github.com/cheng2016/CustomScrollBarSample

如果对你有帮助,欢迎打赏,您的打赏是我分享的最大动力!!

你可能感兴趣的:(android)