文档章节

自定义View----滑动刻度尺与流式布局 实例(四)

KingBoxing123
 KingBoxing123
发布于 2017/04/06 11:16
字数 1709
阅读 52
收藏 0

近在系统学习自定义View这一块的知识,前面几篇基本都是理论知识,这篇博客着重从实战来加强对自定义View的理解与运用。实现的两种效果,分别代表自定义View与自定义ViewGroup。

效果图:

这里写图片描述

上面的是一个可以滑动的刻度尺,支持快速滑动,选择的数字也会显示在下方;下面的是一个经典的流式布局,会根据文字长度自动进行布局。一起看看怎么实现的吧:

一.准备工作

1.布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:myscroll="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_five"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical">


    <org.tyk.android.artstudy.MySelectView
        android:id="@+id/my_selectview"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="50dp"
        myscroll:lineColor="@color/font_text"
        myscroll:textColor="@color/strong"
        myscroll:textSize="20dp"></org.tyk.android.artstudy.MySelectView>

    <TextView
        android:id="@+id/number_txt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="20dp"
        android:text="选择的数字为:"
        android:textSize="20dp" />

    <org.tyk.android.artstudy.MyFlowLayout
        android:id="@+id/my_flowlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="50dp"
        android:background="@color/bg_page">
    </org.tyk.android.artstudy.MyFlowLayout>


</LinearLayout>

 

从上到下的线性布局,依次是滑动刻度尺,数字TextView,流式布局,以及设置了一些自定义的属性。

2.自定义滑动刻度尺的初始准备

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

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

    public MySelectView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);


        //获取我们自定义的样式属性
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MySelectView, defStyleAttr, 0);
        int n = array.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.MySelectView_lineColor:
                    // 默认颜色设置为黑色
                    lineColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MySelectView_textColor:
                    textColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MySelectView_textSize:
                    // 默认设置为16sp,TypeValue也可以把sp转化为px
                    textSize = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
                            TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                    break;
            }
        }


        array.recycle();
        init();
    }

    public void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        bigBound = new Rect();
        smallBound = new Rect();
    }


    public void setmStartWidth(int mStartWidth) {
        this.mStartWidth = mStartWidth;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width;
        int height;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = widthSize * 1 / 2;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = heightSize * 1 / 2;
        }

        setMeasuredDimension(width, height);

    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mWidth = getWidth();
        mHeight = getHeight();
        mStartWidth = 0;
    }

 

初始化自定义控件,获取自定义控件的样式属性,初始化相关工具,重写onMeasure()测量自定义控件大小,重写onLayout()获取自定义控件宽高。

二.自定义滑动刻度尺的实现

1.重写onDraw()方法绘制刻度尺

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(lineColor);
        //画背景
        canvas.drawLine(0, 0, mWidth, 0, mPaint);
        canvas.drawLine(0, mHeight, mWidth, mHeight, mPaint);

        //画数字
        for (int i = 0; i < 1000; i++) {
            if (i % 5 == 0) {
                mPaint.setColor(textColor);
                canvas.drawLine(mStartWidth, 0, mStartWidth, getHeight() / 3, mPaint);
                mPaint.setTextSize(textSize);
                mPaint.getTextBounds(String.valueOf(i), 0, String.valueOf(i).length(), bigBound);
                canvas.drawText(String.valueOf(i), mStartWidth - bigBound.width() / 2, getHeight() / 2 + bigBound.height() * 3 / 4, mPaint);
            } else {
                mPaint.setColor(lineColor);
                mPaint.setTextSize(textSize - 15);
                canvas.drawLine(mStartWidth, 0, mStartWidth, getHeight() / 5, mPaint);
                mPaint.getTextBounds(String.valueOf(i), 0, String.valueOf(i).length(), smallBound);
                canvas.drawText(String.valueOf(i), mStartWidth - smallBound.width() / 2, getHeight() / 2 + smallBound.height() * 3 / 4, mPaint);
            }
            mStartWidth += mWidth / 10;
        }
        //画中间刻度线
        mPaint.setColor(textColor);
        canvas.drawLine(mWidth / 2, 0, mWidth / 2, getHeight() / 3, mPaint);
    }

 

绘制背景的两条实线,绘制中间的数字,绘制中间的刻度线。

2.重写onTouchEvent()方法处理滑动事件

    @Override
    public boolean onTouchEvent(MotionEvent event) {


        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain();
        }
        velocityTracker.addMovement(event);

        int x = (int) event.getX();
        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                xDown = x;
                break;
            case MotionEvent.ACTION_MOVE:
                xMove = x;
                mStartWidth = xScroll + (xMove - xDown);
                invalidate();
                int numberScroll = (int) Math.round(Double.valueOf(mStartWidth) / Double.valueOf(mWidth / 10));
                listener.getNumber(Math.abs(numberScroll - 5));
                break;
            case MotionEvent.ACTION_UP:
                xUp = x;
                xScroll = xScroll + (xUp - xDown);
                //处理快速滑动
                velocityTracker.computeCurrentVelocity(1000);
                int scrollX = (int) velocityTracker.getXVelocity();
                xScroll = xScroll + scrollX;
                ValueAnimator walkAnimator = ValueAnimator.ofInt(mStartWidth, xScroll);
                walkAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        mStartWidth = (int) animation.getAnimatedValue();
                        invalidate();
                    }
                });
                walkAnimator.setDuration(500);
                walkAnimator.start();
                walkAnimator.addListener(new Animator.AnimatorListener() {
                    @Override
                    public void onAnimationStart(Animator animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {

                        //处理惯性滑动
                        int endX = xScroll % (mWidth / 10);
                        if (Math.abs(endX) < mWidth / 20) {
                            xScroll = xScroll - endX;
                            mStartWidth = xScroll;
                            invalidate();
                        } else {
                            xScroll = xScroll + (Math.abs(endX) - mWidth / 10);
                            mStartWidth = xScroll;
                            invalidate();
                        }
                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {

                    }

                    @Override
                    public void onAnimationRepeat(Animator animation) {

                    }
                });
                int number = (int) Math.round(Double.valueOf(xScroll) / Double.valueOf(mWidth / 10));
                listener.getNumber(Math.abs(number - 5));
                break;
        }
        return true;
    }

这一块是整个自定义滑动刻度尺的重点,慢慢分析一下:

初始化VelocityTracker,并且把要追踪的MotionEvent注册到VelocityTracker的监听中,用来跟踪触摸屏事件,主要用来处理滑动刻度尺的快速滑动。

MotionEvent.ACTION_DOWN: 获取水平方向X的坐标

MotionEvent.ACTION_MOVE: 获取水平方向滑动的距离,然后不断改变绘制的开始位置,再调用invalidate()来进行重绘,达到滑动的效果。后面两句代码是为了让下面显示的数字能够实时更新,接口回调。

MotionEvent.ACTION_UP: 
1.获取滑动到总距离。 
2.处理快速滑动,首先获取1秒内X方向所滑动像素值,然后确定最终滑动的位置。通过一个属性动画,不断改变绘制的开始位置,再调用invalidate()来进行重绘,达到快速滑动的效果。 
3.处理惯性滑动,仔细查看效果图你会发现,当最后滑动的终点位置不足一半时,会自动滑动到前一个位置;当最后滑动的终点位置超过一半时,会自动滑动到下一个位置。这里其实就是在动画结束的时候,进行判断,然后调用invalidate()来进行重绘,达到惯性滑动的效果。 
4.最后两句代码是为了让下面显示的数字最终能够实时更新,接口回调。

三.自定义流式布局的实现


    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        // 计算出所有的childView的宽和高
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
        setMeasuredDimension(sizeWidth, sizeHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left = 0;
        int top = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
            int lc = left + params.leftMargin;
            int rc = childView.getMeasuredWidth() + lc;
            int tc = top + params.topMargin;
            int bc = childView.getMeasuredHeight() + tc;
            childView.layout(lc, tc, rc, bc);
            //超过宽度则换行
            if (rc + childView.getMeasuredWidth() > getMeasuredWidth()) {
                left = 0;
                top = bc;
            } else {
                left = rc;
            }
        }
    }

1.重写generateLayoutParams()方法返回MarginLayoutParams的实例,使自定义的流式布局能够支持margin属性 
2.重写onDraw()方法计算出所有的childView的宽和高以及测量模式,并且设置自己的宽高 
3.重写onLayout()方法对所有childView进行定位(设置childView的绘制区域),并且根据childView的宽度进行自动换行。

具体使用:

    public void init() {

        stringList.add("数据库");
        stringList.add("移动开发");
        stringList.add("前端开发");
        stringList.add("微信小程序");
        stringList.add("服务器开发");
        stringList.add("PHP");
        stringList.add("人工智能");
        stringList.add("大数据");
        mySelectView = (MySelectView) findViewById(R.id.my_selectview);
        myFlowLayout = (MyFlowLayout) findViewById(R.id.my_flowlayout);
        for (String textView : stringList) {
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT);
            params.setMargins(40, 40, 40, 40);
            TextView showText = new TextView(this);
            showText.setLayoutParams(params);
            showText.setTextColor(getResources().getColor(R.color.text_color));
            showText.setTextSize(20);
            showText.setText(textView);
            showText.setBackground(getResources().getDrawable(R.drawable.flag_01));
            myFlowLayout.addView(showText);
        }
        numberTxt = (TextView) findViewById(R.id.number_txt);
        mySelectView.setListener(this);
    }

将需要设置的文字动态添加到我们的流式布局中去即可,自定义的流式布局会自动根据添加文字的大小进行布局,达到最后的效果。

源码地址:

https://github.com/18722527635/AndroidArtStudy

© 著作权归作者所有

KingBoxing123
粉丝 5
博文 95
码字总数 48684
作品 0
成都
私信 提问
【Android】掌握自定义LayoutManager(二) 实现流式布局

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 转载请标明出处: http://blog.csdn.net/zxt0601/article/details/52956504 本文出自:【张旭童的博客】 本系列文章相关代码传送门:...

zxt0601
2016/10/28
0
0
wzyitspider/Tunlview

##一、前言 最近公司需要,需要一个视频播放条控件,可以将控件与时间对应起来,也可以对控件进行移动和缩放。(PS:公司是做摄像头的)移动到某个位置就播放该位置对应时间戳的视频录像,当...

wzyitspider
2017/10/27
0
0
Android自定义滑动刻度尺

一 功能: 自定义View实现跟随手指滚动的刻度尺,实现了类似SeekBar的滑动选中效果。项目地址,欢迎star! UI图: 功能: 通过设置最小值跟最大值的范围,以及offset值。View将根据这些数据去...

那个人
04/02
0
0
仿夸克浏览器底部工具栏

夸克浏览器是我非常喜欢的一款浏览器,整体非常简洁,UI做的也很精致。今天我就来仿写下其中的底部工具栏。先来看看原本的效果: 本身就是一个弹框,特别之处就是可以收缩伸展布局,再来看看...

大头呆
2017/12/08
0
0
Android中常见的热门标签的流式布局的实现

一、概述: 在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出) ...

拉偶有所依
2015/04/14
870
4

没有更多内容

加载失败,请刷新页面

加载更多

只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常

统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生。 比较通用的返回值格式如下:...

晓月寒丶
今天
58
0
区块链应用到供应链上的好处和实际案例

区块链可以解决供应链中的很多问题,例如记录以及追踪产品。那么使用区块链应用到各产品供应链上到底有什么好处?猎头悬赏平台解优人才网小编给大家做个简单的分享: 使用区块链的最突出的优...

猎头悬赏平台
今天
27
0
全世界到底有多少软件开发人员?

埃文斯数据公司(Evans Data Corporation) 2019 最新的统计数据(原文)显示,2018 年全球共有 2300 万软件开发人员,预计到 2019 年底这个数字将达到 2640万,到 2023 年达到 2770万。 而来自...

红薯
今天
61
0
Go 语言基础—— 通道(channel)

通过通信来共享内存(Java是通过共享内存来通信的) 定义 func service() string {time.Sleep(time.Millisecond * 50)return "Done"}func AsyncService() chan string {retCh := mak......

刘一草
今天
57
0
Apache Flink 零基础入门(一):基础概念解析

Apache Flink 的定义、架构及原理 Apache Flink 是一个分布式大数据处理引擎,可对有限数据流和无限数据流进行有状态或无状态的计算,能够部署在各种集群环境,对各种规模大小的数据进行快速...

Vincent-Duan
今天
58
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部