【Android】自定义View —— 滑动的次数选择器

【关键词】

自定义View 次数选择器 滑动


【问题】

  • 实现一个可滑动的次数选择器;

【效果图】

「原型图」

「实现图」

【分析】

  • 对外提供简单的Change监听接口;
  • 如果处于两者之间就需要做判断:大于一半就自动跳转到下一个,小于一半,则回到上一个;
  • 通过Scroller及其startScroll()方法来实现回弹效果;
  • 要灵活控制刻度的最小值和最大值,因为可能随着需求的更改,这个值很容易发生改变(即处理好对应关系);
  • 在自定义View中只画刻度和文字,至于红色的指针和外面的透明渐变图层则可以直接在布局文件中实现:

    一方面比起在java代码中更容易实现,另一方面更灵活(如果想要去掉渐变图层,只需要修改布局文件,而不需要对java代码进行任何修改);

【解决方案】

  • 我最开始是通过不断的设置padding来实现,但是一时头脑不清晰,没有处理好对应关系,就重新改用scroll来实现了;

【代码】

「客户端用法代码」

布局代码activity_count_view.xml


" data-snippet-id="ext.034c2d7ad689e18b2717192d3945a52a" data-snippet-saved="false" data-codota-status="done"><?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:background="#ffffff"
    android:orientation="vertical">
    <!-- 刻度 -->
    <com.lyloou.android.view.CountView
        android:id="@+id/cv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:layout_marginEnd="48dp"
        android:layout_marginStart="48dp"
        android:background="#ffffff" />

    <!-- 中间的指针 -->
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@mipmap/reminder_slider" />

    <!-- 遮罩层 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:orientation="horizontal">
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:background="#ffffff" />
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="4"
            android:background="@drawable/gradient_count_view" />
        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:background="#ffffff" />
    </LinearLayout>

</RelativeLayout>

Activity对布局文件的初始化

public class CountViewActivity extends AppCompatActivity {
    private Activity mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        setContentView(R.layout.activity_count_view);
        initView();
    }

    private void initView() {
        CountView countView = (CountView) findViewById(R.id.cv_main);
        if (countView != null) {
            // int index = countView.getIndex(); // 获取当前值
            // countView.setIndex(6);// 设置默认值
            countView.setOnChangeListener(new CountView.OnChangeListener() {
                @Override
                public void doIndex(int index) {
                    Utoast.show(mContext, "选中了:" + index + "次");
                }
            });
        }
    }
}

中间的渐变shape gradient_count_view.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <gradient
        android:angle="180"
        android:centerColor="#10ffffff"
        android:endColor="#ffffff"
        android:startColor="#ffffff" />

</shape>

「通用源代码」

自定义View

= (MAX_ITEM - MIN_ITEM)) {
            index = MAX_ITEM - MIN_ITEM;
            smoothScrollTo(index * W);
        } else {
            int shift = scrollX % W;
            if (shift >= W / 2) {
                index++;
                smoothScrollBy(W - shift);
            } else {
                smoothScrollBy(-shift);
            }
        }
        return index;
    }

    public void setIndex(int index) {
        int newIndex = index - MIN_ITEM;
        scrollTo(newIndex * W, 0);
    }

    public void setOnChangeListener(OnChangeListener listener) {
        mChangeListener = listener;
    }

    private void smoothScrollTo(int dextX) {
        smoothScrollBy(dextX - getScrollX());
    }

    private void smoothScrollBy(int deltaX) {
        mScroller.startScroll(getScrollX(), 0, deltaX, 0, 320);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller != null && mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

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

        int shift = getWidth() / 2 - W / 2; // 将原点移到屏幕中间;
        int height = getHeight();

        for (int i = MIN_ITEM - EXTRA_COUNT; i = MIN_ITEM && i package com.lyloou.android.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

import com.lyloou.android.util.Uscreen;

public class CountView extends View {

    public interface OnChangeListener {
        void doIndex(int index);
    }

    private final Context CONTEXT = getContext();
    private final int W = Uscreen.dp2Px(CONTEXT, 36);// 每一项的固定长度
    private final int LINE_H = Uscreen.dp2Px(CONTEXT, 6); // 长直线的厚度
    private final int ITEM_LINE_W = LINE_H / 3; // 竖直刻度直线的厚度
    private final int ITEM_LINE_H = Uscreen.dp2Px(CONTEXT, 16); // 竖直刻度直线的高度
    private final int BOTTOM_FONT_SIZE = (int) Uscreen.sp2Px(CONTEXT, 12); // 底部的文字大小

    private static final int COLOR_LINE = Color.parseColor("#4422f9"); // 长直线和刻度线的颜色
    private static final int MIN_ITEM = 1; // 显示的最小刻度值
    private static final int MAX_ITEM = 20; // 显示的最大刻度值
    private static final int EXTRA_COUNT = 2; // 左边和右边额外的刻度个数

    private Paint mLinePaint;
    private TextPaint mTextPaint;
    private Scroller mScroller;
    private OnChangeListener mChangeListener;

    public CountView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setUp();
        initData();
    }

    public CountView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CountView(Context context) {
        this(context, null);
    }

    private void setUp() {
        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLinePaint.setColor(COLOR_LINE);

        mTextPaint = new TextPaint();
        mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(BOTTOM_FONT_SIZE);

        mScroller = new Scroller(CONTEXT);
    }

    private void initData() {
        setIndex(MIN_ITEM);
    }

    private int mLastX;

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                int x = (int) event.getX();
                int deltaX = x - mLastX;
                scrollBy(-deltaX, 0);
                mLastX = x;
                break;
            case MotionEvent.ACTION_UP:
                doIndex(getIndex());
                break;
        }
        return true;
    }

    private void doIndex(int index) {
        if (mChangeListener != null) {
            mChangeListener.doIndex(index + MIN_ITEM);
        }
    }

    public int getIndex() {
        int index = 0;
        int scrollX = getScrollX();
        index = scrollX / W;
        if (scrollX <= 0) {
            index = 0;
            smoothScrollTo(index);
        } else if (index >= (MAX_ITEM - MIN_ITEM)) {
            index = MAX_ITEM - MIN_ITEM;
            smoothScrollTo(index * W);
        } else {
            int shift = scrollX % W;
            if (shift >= W / 2) {
                index++;
                smoothScrollBy(W - shift);
            } else {
                smoothScrollBy(-shift);
            }
        }
        return index;
    }

    public void setIndex(int index) {
        int newIndex = index - MIN_ITEM;
        scrollTo(newIndex * W, 0);
    }

    public void setOnChangeListener(OnChangeListener listener) {
        mChangeListener = listener;
    }

    private void smoothScrollTo(int dextX) {
        smoothScrollBy(dextX - getScrollX());
    }

    private void smoothScrollBy(int deltaX) {
        mScroller.startScroll(getScrollX(), 0, deltaX, 0, 320);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller != null && mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

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

        int shift = getWidth() / 2 - W / 2; // 将原点移到屏幕中间;
        int height = getHeight();

        for (int i = MIN_ITEM - EXTRA_COUNT; i < MAX_ITEM + EXTRA_COUNT; i++) {
            int startX = (i - MIN_ITEM) * W + shift;

            // 画直线
            mLinePaint.setStrokeWidth(LINE_H);
            canvas.drawLine(startX, height / 2, startX + W, height / 2, mLinePaint);

            // 画刻度
            mLinePaint.setStrokeWidth(ITEM_LINE_W);
            canvas.drawLine(startX + W / 2, height / 2 - ITEM_LINE_H, startX + W / 2, height / 2, mLinePaint);

            // 在刻度范围内,才画文字
            if (i >= MIN_ITEM && i <= MAX_ITEM) {
                String text = i + "次";
                float textWidth = mTextPaint.measureText(text);
                float textHeight = mTextPaint.getFontMetrics().bottom;
                canvas.drawText(text, startX + (W - textWidth) / 2, (height + textHeight) / 2 + textHeight * 10,
                        mTextPaint);
            }
        }
    }
}

【参考资料】

  • 回弹效果的代码参考了《Android开发艺术探索》P136
时间: 2024-07-29 20:26:23

【Android】自定义View —— 滑动的次数选择器的相关文章

Android自定义View滑动事件处理总结

滑动处理需要用到的各种工具类: android.view.VelocityTracker android.view.OverScroller android.view.ViewConfiguration VelocityTracker类  主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率. 用addMovement(MotionEvent)函数将Motion event加入到VelocityTracker类实例中.你可以使用getXVelocity() 或getX

Android自定义View(二)

前言 魅族手机的闹钟应用中有个倒计时,这个控件还是蛮有趣的.左边是魅族闹钟,右边是我们最终实现的效果,虽然有些细节还需优化,不过基本上已经达到了想要的效果,我们先来就来看看如何实现吧. 分析 确定宽高 对一个Android自定义控件来说,一般都经过三个步骤 onLayout() onMeasure() onDraw() onLayout明确子控件在父控件中的位置(本控件不需要重写),onMeasure是确定控件的大小(宽.高),而onDraw是我们重点关注的方法,我们需要在这个方法中写入显示Vi

android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索

我们的手机通讯录一般都有这样的效果,如下图: OK,这种效果大家都见得多了,基本上所有的Android手机通讯录都有这样的效果.那我们今天就来看看这个效果该怎么实现. 一.概述 1.页面功能分析 整体上来说,左边是一个ListView,右边是一个自定义View,但是左边的ListView和我们平常使用的ListView还有一点点不同,就是在ListView中我对所有的联系人进行了分组,那么这种效果的实现最常见的就是两种思路: 1.使用ExpandableListView来实现这种分组效果 2.使

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~

Android自定义View(RollWeekView-炫酷的星期日期选择控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义CustomWeekView 4重写onMeasure 5点击后执行动画 7重置预备控件 源码下载 ??最近收到一个自定义控件的需求,需要做一个日期选择控件,实现图如下: ???? ??一次展示一个星期的5天,中间放大的为当前选中的:如果点击了其中一个日期,比如星期五,那么整体向左滑动,并将星期五慢慢放大

android自定义View之NotePad出鞘记

现在我们的手机上基本都会有一个记事本,用起来倒也还算方便,记事本这种东东,如果我想要自己实现,该怎么做呢?今天我们就通过自定义View的方式来自定义一个记事本.OK,废话不多说,先来看看效果图. 整个页面还是很简单的. 1.自定义View的分类 OK,那么在正文开始之前,我想先来说说自定义View的分类,自定义View我们一共分为三类 1.自绘控件 自绘控件就是我们自定义View继承自已有控件,然后扩展其功能,之前两篇自定义View的博客(android自定义View之钟表诞生记,android

Android 自定义View合集

自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/Mr-XiaoLiang 自定义控件三部曲 http://blog.csdn.net/harvic880925?viewmode=contents Android 从0开始自定义控件之View基础知识与概念 http://blog.csdn.net/airsaid/article/details/5

Android自定义View(一)

mnesia在频繁操作数据的过程可能会报错:** WARNING ** Mnesia is overloaded: {dump_log, write_threshold},可以看出,mnesia应该是过载了.这个警告在mnesia dump操作会发生这个问题,表类型为disc_only_copies .disc_copies都可能会发生. 如何重现这个问题,例子的场景是多个进程同时在不断地mnesia:dirty_write/2 mnesia过载分析 1.抛出警告是在mnesia 增加dump

Android自定义View——自定义搜索框(SearchView)

概述 在Android开发中,当系统数据项比较多时,常常会在app添加搜索功能,方便用户能快速获得需要的数据.搜索栏对于我们并不陌生,在许多app都能见到它,比如豌豆荚 在某些情况下,我们希望我们的自动补全信息可以不只是纯文本,还可以像豌豆荚这样,能显示相应的图片和其他数据信息,因此Android给我们提供的AutoCompleteTextView往往就不够用,在大多情况下我们都需要自己去实现搜索框. 分析 根据上面这张图,简单分析一下自定义搜索框的结构与功能,有 1. 搜索界面大致由三部门组成