Material Design学习之 Sliders(详细分析,悬空气球显示进度值,附带Eclipse可以jar)

转载请注明出处:王亟亟的大牛之路

Material Design系列的文章这是第五篇,今天讲滑块控件(Sliders)

之前的传送门:http://blog.csdn.net/ddwhan0123/article/details/50578348(代码实现都靠画,学好View还是很重要的)



老规矩,先说下理论部分

滑块控件(Sliders,简称滑块)可以让我们通过在连续或间断的区间内滑动锚点来选择一个合适的数值。区间最小值放在左边,对应的,最大值放在右边。滑块(Sliders)可以在滑动条的左右两端设定图标来反映数值的强度。这种交互特性使得它在设置诸如音量、亮度、色彩饱和度等需要反映强度等级的选项时成为一种极好的选择。

这里贴一下我们的实现效果:

再贴一下官方的实现

当然,这个和其他控件一样,都有一套暗色主题的配色.

使用场景?

进度拖拽,选择范围值等。

为何使用?

引领用户返回合理的内容(输入的话出错率比较高,也很麻烦)

当然还有一种是可输入可滑动的,像这样:

文中例子实现是根据这个:

它的暗色主题是这样

原文地址:http://www.google.com/design/spec/components/sliders.html



再一部分是代码实现,老规矩,贴贴代码结构(这一系列的所有“栗子”,我都是单一做包的,方便大家使用)

另外2个类在之前的代码中已经解释过了,大家可以往前翻

主要说下Slider这个类

public class Slider extends CustomView

28行,继承于一个自定义的RelativeLayout

 private int backgroundColor = Color.parseColor("#4CAF50");
    private Ball ball;
    private Bitmap bitmap;
    private int max = 100;
    private int min = 0;
    private NumberIndicator numberIndicator;
    private OnValueChangedListener onValueChangedListener;
    private boolean placedBall = false;
    private boolean press = false;
    private boolean showNumberIndicator = false;
    private int value = 0;

    public Slider(Context context, AttributeSet attrs) {
        super(context, attrs);
        setAttributes(attrs);
    }

30-46行,构造函数调用初始化的方法,声明一系列所需变量,什么最大值最小值,是否显示气球什么的,补充一点这里有一个自己写的回调,之后会解释。

     public int getMax() {
        return max;
    }

    public void setMax(int max) {
        this.max = max;
    }

    public int getMin() {
        return min;
    }

    public void setMin(int min) {
        this.min = min;
    }

48-62行,一系列的set get方法,主要用于设置范围。

     public OnValueChangedListener getOnValueChangedListener() {
        return onValueChangedListener;
    }

    public void setOnValueChangedListener(
            OnValueChangedListener onValueChangedListener) {
        this.onValueChangedListener = onValueChangedListener;
    }

64-71,接口的set get方法,继续看下去。

 public void setValue(final int value) {
        if (placedBall == false)
            post(new Runnable() {

                @Override
                public void run() {
                    setValue(value);
                }
            });
        else {
            this.value = value;
            float division = (ball.xFin - ball.xIni) / max;
            ViewHelper.setX(ball,
                    value * division + getHeight() / 2 - ball.getWidth() / 2);
            ball.changeBackground();
        }

    }

79-96行,设置进度的实现,如果中间变量判断为非初始化操作的时候,那么就绘制具体 进度条的触控点“圆”的位置,并且绘制条的颜色,球的状态等内容。
93行是具体改变状态的操作。
(会讲中间变量的操作,这里不理解可以带着疑问继续看下去)

      @Override
    public void invalidate() {
        ball.invalidate();
        super.invalidate();
    }

98-102行,刷新UI

  public boolean isShowNumberIndicator() {
        return showNumberIndicator;
    }

    public void setShowNumberIndicator(boolean showNumberIndicator) {
        this.showNumberIndicator = showNumberIndicator;
        numberIndicator = (showNumberIndicator) ? new NumberIndicator(
                getContext()) : null;
    }

104-112,是否显示/设置 气泡球+进度值的操作

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        isLastTouch = true;
        if (isEnabled()) {
            if (event.getAction() == MotionEvent.ACTION_DOWN
                    || event.getAction() == MotionEvent.ACTION_MOVE) {
                if (numberIndicator != null
                        && numberIndicator.isShowing() == false)
                    numberIndicator.show();
                if ((event.getX() <= getWidth() && event.getX() >= 0)) {
                    press = true;
                    // calculate value
                    int newValue = 0;
                    float division = (ball.xFin - ball.xIni) / (max - min);
                    if (event.getX() > ball.xFin) {
                        newValue = max;
                    } else if (event.getX() < ball.xIni) {
                        newValue = min;
                    } else {
                        newValue = min + (int) ((event.getX() - ball.xIni) / division);
                    }
                    if (value != newValue) {
                        value = newValue;
                        if (onValueChangedListener != null)
                            onValueChangedListener.onValueChanged(newValue);
                    }
                    // move ball indicator
                    float x = event.getX();
                    x = (x < ball.xIni) ? ball.xIni : x;
                    x = (x > ball.xFin) ? ball.xFin : x;
                    ViewHelper.setX(ball, x);
                    ball.changeBackground();

                    // If slider has number indicator
                    if (numberIndicator != null) {
                        // move number indicator
                        numberIndicator.indicator.x = x;
                        numberIndicator.indicator.finalY = Utils
                                .getRelativeTop(this) - getHeight() / 2+80;
                        numberIndicator.indicator.finalSize = getHeight() / 2;
                        numberIndicator.numberIndicator.setText("");
                    }

                } else {
                    press = false;
                    isLastTouch = false;
                    if (numberIndicator != null)
                        numberIndicator.dismiss();

                }

            } else if (event.getAction() == MotionEvent.ACTION_UP ||
                    event.getAction() == MotionEvent.ACTION_CANCEL) {
                if (numberIndicator != null)
                    numberIndicator.dismiss();
                isLastTouch = false;
                press = false;
            }
        }
        return true;
    }

114-174,具体滑动实现的操作,分析下流程

在手指按下去/挪动的时候判断是否有气泡,如果有就显示,如果手指的操作有效就产生一个数值如果挪动无效/没有挪动就还是最小值,在值有效地情况下把这个数值利用 onValueChangedListener.onValueChanged(newValue);传出去。在挪动的挪动的过程中或者挪动成功后还要改变ball的样式和效果。以及呈现我们的气球和为之所对应的数值。

如果操作手指离开/取消 控件区域 隐藏我们的气泡

     @Override
    public void setBackgroundColor(int color) {
        backgroundColor = color;
        if (isEnabled())
            beforeBackground = backgroundColor;
    }

176-181 设置颜色

   @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (!placedBall) {
            placeBall();
        }

        Paint paint = new Paint();

        if (value == min) {
            // Crop line to transparent effect

            if (bitmap == null) {
                bitmap = Bitmap.createBitmap(canvas.getWidth(),
                        canvas.getHeight(), Bitmap.Config.ARGB_8888);
            }
            Canvas temp = new Canvas(bitmap);
            paint.setColor(Color.parseColor("#B0B0B0"));
            paint.setStrokeWidth(Utils.dpToPx(2, getResources()));
            temp.drawLine(getHeight() / 2, getHeight() / 2, getWidth()
                    - getHeight() / 2, getHeight() / 2, paint);
            Paint transparentPaint = new Paint();
            transparentPaint.setColor(getResources().getColor(
                    android.R.color.transparent));
            transparentPaint.setXfermode(new PorterDuffXfermode(
                    PorterDuff.Mode.CLEAR));
            temp.drawCircle(ViewHelper.getX(ball) + ball.getWidth() / 2,
                    ViewHelper.getY(ball) + ball.getHeight() / 2,
                    ball.getWidth() / 2, transparentPaint);

            canvas.drawBitmap(bitmap, 0, 0, new Paint());
        } else {

            paint.setColor(Color.parseColor("#B0B0B0"));
            paint.setStrokeWidth(Utils.dpToPx(2, getResources()));
            canvas.drawLine(getHeight() / 2, getHeight() / 2, getWidth()
                    - getHeight() / 2, getHeight() / 2, paint);
            paint.setColor(backgroundColor);
            float division = (ball.xFin - ball.xIni) / (max - min);
            int value = this.value - min;

            canvas.drawLine(getHeight() / 2, getHeight() / 2, value * division
                    + getHeight() / 2, getHeight() / 2, paint);

        }

        if (press && !showNumberIndicator) {
            paint.setColor(backgroundColor);
            paint.setAntiAlias(true);
            canvas.drawCircle(ViewHelper.getX(ball) + ball.getWidth() / 2,
                    getHeight() / 2, getHeight() / 3, paint);
        }
        invalidate();
    }

198-252,具体绘制的操作

一开始就对我们之前的那个中间值做了个判断,为ture就是初始化位置。

然后就是构建了画笔然后判断是否在最小值,如果是的话在最小值哪里画个空心圆,如果不是就在value的实际值位置画个实心圆,然后value值到最小值的位置涂抹颜色。如果,有气泡并且被解除,再画个圆。

 // Set atributtes of XML to View
    protected void setAttributes(AttributeSet attrs) {

        setBackgroundResource(R.drawable.background_transparent);

        // Set size of view
        setMinimumHeight(Utils.dpToPx(48, getResources()));
        setMinimumWidth(Utils.dpToPx(80, getResources()));

        // Set background Color
        // Color by resource
        int bacgroundColor = attrs.getAttributeResourceValue(ANDROIDXML,
                "background", -1);
        if (bacgroundColor != -1) {
            setBackgroundColor(getResources().getColor(bacgroundColor));
        } else {
            // Color by hexadecimal
            int background = attrs.getAttributeIntValue(ANDROIDXML, "background", -1);
            if (background != -1)
                setBackgroundColor(background);
        }

        showNumberIndicator = attrs.getAttributeBooleanValue(MATERIALDESIGNXML,
                "showNumberIndicator", false);
        min = attrs.getAttributeIntValue(MATERIALDESIGNXML, "min", 0);
        max = attrs.getAttributeIntValue(MATERIALDESIGNXML, "max", 0);
        value = attrs.getAttributeIntValue(MATERIALDESIGNXML, "value", min);

        ball = new Ball(getContext());
        RelativeLayout.LayoutParams params = new LayoutParams(Utils.dpToPx(20,
                getResources()), Utils.dpToPx(20, getResources()));
        params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
        ball.setLayoutParams(params);
        addView(ball);

        // Set if slider content number indicator
        // TODO
        if (showNumberIndicator) {
            numberIndicator = new NumberIndicator(getContext());
        }

    }

254-295行,之前构造函数调用的初始化的方法,一系列的获取XML初始化的参数,当然如果没有就设置默认了

 private void placeBall() {
        ViewHelper.setX(ball, getHeight() / 2 - ball.getWidth() / 2);
        ball.xIni = ViewHelper.getX(ball);
        ball.xFin = getWidth() - getHeight() / 2 - ball.getWidth() / 2;
        ball.xCen = getWidth() / 2 - ball.getWidth() / 2;
        placedBall = true;
    }

297-203,具体初值位置的操作。

  public interface OnValueChangedListener {
        public void onValueChanged(int value);
    }

306-308,我们回调的接口,返回的是我们的进度值

 class Ball extends View {

        float xIni, xFin, xCen;

        public Ball(Context context) {
            super(context);
            setBackgroundResource(R.drawable.background_switch_ball_uncheck);
        }

        public void changeBackground() {
            if (value != min) {
                setBackgroundResource(R.drawable.background_checkbox);
                LayerDrawable layer = (LayerDrawable) getBackground();
                GradientDrawable shape = (GradientDrawable) layer
                        .findDrawableByLayerId(R.id.shape_bacground);
                shape.setColor(backgroundColor);
            } else {
                setBackgroundResource(R.drawable.background_switch_ball_uncheck);
            }
        }

    }

310-331,我们的进度小球的实现。

   class Indicator extends RelativeLayout {

        boolean animate = true;
        // Final size after animation
        float finalSize = 0;
        // Final y position after animation
        float finalY = 0;
        boolean numberIndicatorResize = false;
        // Size of number indicator
        float size = 0;
        // Position of number indicator
        float x = 0;
        float y = 0;

        public Indicator(Context context) {
            super(context);
            setBackgroundColor(getResources().getColor(
                    android.R.color.transparent));
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);

            if (numberIndicatorResize == false) {
                LayoutParams params = (LayoutParams) numberIndicator.numberIndicator
                        .getLayoutParams();
                params.height = (int) finalSize * 2;
                params.width = (int) finalSize * 2;
                numberIndicator.numberIndicator.setLayoutParams(params);
            }

            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setColor(backgroundColor);
            if (animate) {
                if (y == 0)
                    y = finalY + finalSize * 2;
                y -= Utils.dpToPx(6, getResources());
                size += Utils.dpToPx(2, getResources());
            }
            canvas.drawCircle(
                    ViewHelper.getX(ball)
                            + Utils.getRelativeLeft((View) ball.getParent())
                            + ball.getWidth() / 2, y, size, paint);
            if (animate && size >= finalSize)
                animate = false;
            if (animate == false) {
                ViewHelper
                        .setX(numberIndicator.numberIndicator,
                                (ViewHelper.getX(ball)
                                        + Utils.getRelativeLeft((View) ball
                                        .getParent()) + ball.getWidth() / 2)
                                        - size);
                ViewHelper.setY(numberIndicator.numberIndicator, y - size);
                numberIndicator.numberIndicator.setText(value + "");
            }

            invalidate();
        }

    }

335-396,气泡的实现。

先初始化位置,声明画笔,计算尺寸,画圆,传值给数值控件,不断的根据手势位置移动而移动。

    class NumberIndicator extends Dialog {

        Indicator indicator;
        TextView numberIndicator;

        public NumberIndicator(Context context) {
            super(context, android.R.style.Theme_Translucent);
        }

        @Override
        public void dismiss() {
            super.dismiss();
            indicator.y = 0;
            indicator.size = 0;
            indicator.animate = true;
        }

        @Override
        public void onBackPressed() {
        }

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            super.onCreate(savedInstanceState);
            setContentView(R.layout.number_indicator_spinner);
            setCanceledOnTouchOutside(false);

            RelativeLayout content = (RelativeLayout) this
                    .findViewById(R.id.number_indicator_spinner_content);
            indicator = new Indicator(this.getContext());
            content.addView(indicator);

            numberIndicator = new TextView(getContext());
            numberIndicator.setTextColor(Color.WHITE);
            numberIndicator.setGravity(Gravity.CENTER);
            content.addView(numberIndicator);

            indicator.setLayoutParams(new RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.FILL_PARENT,
                    RelativeLayout.LayoutParams.FILL_PARENT));
        }

    }

398-441行,单纯的实现了一个显示进度值的实现,并不依存于外层的气球,但是会根据气球的状态而改变,其实本身就是一个TextView。


大体分为2部分:

- 进度条+进度条的小球
    - 一开始停留在最小处,空心,自身位置到最大值的条颜色为灰色。
    - 在操作有效的情况下小球UI变化,进度条颜色变化。
- 气泡+气泡的进度值
    - 一开始不显示气泡+进度值。
    - 在操作有效的情况下,气泡与进度值呈现,并且跟随小球的位置移动。

源码地址:https://github.com/ddwhan0123/BlogSample/blob/master/MaterialDesignSliders.zip

Eclipse的小伙伴可能还需要:https://github.com/ddwhan0123/BlogSample/blob/master/JAR/nineoldandroids-2.4.0.jar

时间: 2024-08-02 05:47:42

Material Design学习之 Sliders(详细分析,悬空气球显示进度值,附带Eclipse可以jar)的相关文章

Material Design学习之 Button(详细分析,传说中的水滴动画)

转载请注明出处:王亟亟的大牛之路       上一篇大致介绍了Material Design的一些基本概念传送门:http://blog.csdn.net/ddwhan0123/article/details/50541561 这一片来具体学习下里面的内容,这篇分为两部分一部分是原理分析,一部分是代码分析. 先简要的介绍一些理论知识,顺便温顾下基础知识 按钮 按钮由文字和/或图标组成,文字及图标必须能让人轻易地和点击后展示的内容联系起来. 主要的按钮有三种: 悬浮响应按钮(Floating ac

Material Design学习之 Snackbars(详细分析,Toast的加强版)

转载请注明出处:王亟亟的大牛之路 昨天把Material Design Button部分的内容分析完了,不知道大家理解了他的实现没有.如果没看的话,可以看下,传送门:http://blog.csdn.net/ddwhan0123/article/details/50555958 这几篇关于Material Design文章的代码几乎都是Git上摘录的,我做的事主要是分享给大家+解释分析. 昨天有小伙伴看完后希望我像以前一样把按钮那一部分的代码单独提取出来单独打成一个包,想单独使用或者学习而不是去

Material Design学习之 CheckBox(详细分析,富有表现力)

转载请注明出处:王亟亟的大牛之路 这些天一直在讲Material Design控件的内容,今天继续,说说CheckBox(妈蛋,好冷),上一篇的传送门:http://blog.csdn.net/ddwhan0123/article/details/50560638 老规矩,两部分,第一部分理论知识,第二部分代码 选择控制器 选择控制器允许用户选择选项.有三种类型:复选框.单选框以及开/关切换.选择控制器使用主题同样的颜色.(待会的代码主要讲的是单选按钮) 复选框 单选按钮 切换开关 官方对呈现的

Material Design学习之 Switch(详细解释)

转载请注明出处:王亟亟的大牛之路 继续这一系列的Material Design之行,昨天讲的是Sliders链接如下:http://blog.csdn.net/ddwhan0123/article/details/50586510 今天讲的是Switch,本来有考虑把它和CheckBox一起做了,但是毕竟实现不同,还是分开做吧,废话不多,开始正题 开关 On/off 开关切换单一设置选择的状态.开关控制的选项以及它的状态,应该明确的展示出来并且与内部的标签相一致.开关应该单选按钮呈现相同的视觉特

Material Design学习之 ProgreesBar

转载奇怪注明出处:王亟亟的大牛之路 继续我们Material Design的内容,这一篇讲的是进度条,上一篇是Switch地址如下:http://blog.csdn.net/ddwhan0123/article/details/50592579 进度和动态 在用户可以查看并与内容进行交互之前,尽可能地减少视觉上的变化,尽量使应用加载过程令人愉快.每次操作只能由一个活动指示器呈现,例如,对于刷新操作,你不能即用刷新条,又用动态圆圈来指示. 指示器类型 在操作中,对于完成部分可以确定的情况下,使用确

Material Design学习之 Camera

转载请注明出处:王亟亟的大牛之路 年后第一篇,自从来了某司产量骤减,这里批评下自己,这一篇的素材来源于老牌Material Design控件写手afollestad的 https://github.com/afollestad/material-camera 开篇前,继续安利,你懂的:https://github.com/ddwhan0123/Useful-Open-Source-Android (最近把 6.0授权部分单独罗列出来了) 介绍代码之前先贴下效果图 如何使用 先是添加依赖让mave

Material Design学习之 EditText (功能强大,优于系统自带,感谢“扔物线”)

转载请注明出处:王亟亟的大牛之路 继续之前的Material Design历程,今天是EditText,素材来源于http://www.rengwuxian.com/post/materialedittext(那么代码解释部分大家可以看原作者的文档,我在这里把理论知识灌输下就OK了,作者做的很全面,我都不知道要讲什么了 只能6666666) 大牛的这个库已经有了广泛的认知度和认可,EditText部分就拿他的作为比较推崇的演示版本. 因为大牛已经做了Jar包的支持,所以平时的拆的工作都省了,要直

Material Design学习之 Bottom Sheets (顺便提提CoordinatorLayout)

转载请注明出处:王亟亟的大牛之路 昨天连续上了2篇介绍第三方库的文章,正直好久没提交自己写东西了,那么就补一篇之前MD系列漏的部分 Bottom Sheets Bottom Sheets–底部动作条 底部动作条(Bottom Sheets)是一个从屏幕底部边缘向上滑出的一个面板,使用这种方式向用户呈现一组功能.底部动作条呈现了简单.清晰.无需额外解释的一组操作. 在一个标准的列表样式的底部动作条(Bottom Sheets)中,每一个操作应该有一句描述和一个左对齐的 icon.如果需要的话,也可

Material Design学习之 Dialog(顺便把前两天AppBarLayout没讲的部分提一提)

转载请注明出处:王亟亟的大牛之路 继续之前的MD系列的内容,今天说Dialog,不知道还能翻几篇,反正这一系列都说完了话就找点别的内容整整. Dialogs (提示框)用于提示用户作一些决定,或者是完成某个任务时需要的一些其它额外的信息. Dialog可以是用一种 取消/确定 的简单应答模式,也可以是自定义布局的复杂模式,比如说一些文本设置或者是文本输入 . 官方的呈现,像这样 Dialog 包含了一个标题(可选),内容 ,事件. 标题:主要是用于简单描述下选择类型.它是可选的,要需要的时候赋值