Android可滑动的分时图以及常用动画

先看一下效果:

自定义View

其中顶部是模仿的股票数据分时图,以前也写过详细的文章传送门,只不过不支持左右滑动,这款是在那个基础上的修改

在说一下分时图的思路吧:

  1. 可以看作是一条条相连的直线首尾相接,通过不断给View数据,达到动态添加的效果
  2. 左右滑动也没有想象中那么复杂,我们有所有的分时图数据集合,当屏幕显示不开时,我只选取最新的一段数据给View显示出来,当右滑时,我截取对应的的数据段让View显示,就可以达到滑动效果

其中滑动的核心代码如下:
mShowList 为需要显示的集合
mTimeList 为全部数据集合
通过 subList 截取需要的数据段,截取后实时去刷新当前View

    /**
     * 滑动监听
     * */
    //当前的X轴坐标,我要记录上次滑动的最后坐标,才能实时判断是否左右滑动
    private float startTouchX = 0f; 
    //滑动的距离,通过他来截取对应的数据段
    private int moveTouchX = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                //手指按下
                Log.e(TAG, "onTouchEvent: 123456789->按下");
                //moveTouchX = 0;
                startTouchX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mTimeList.size() > 50){
                    stopRe = true;
                    //手指移动
                    if (startTouchX > event.getX()){
                        Log.e(TAG, "onTouchEvent: 123456789->向左滑动--" + moveTouchX);
                        if (moveTouchX > 0){
                            moveTouchX = moveTouchX - 1;
                            mShowList.clear();
                            mShowList.addAll(mTimeList.subList(mTimeList.size() - (50 + moveTouchX), mTimeList.size() - moveTouchX));
                            postInvalidate();
                        }else {
                            moveTouchX = 0;
                            stopRe = false;
                        }
                    }else {
                        Log.e(TAG, "onTouchEvent: 123456789->向右滑动---" + moveTouchX);
                        if (mTimeList.size() > (50 + moveTouchX)){
                            moveTouchX = moveTouchX + 1;
                            mShowList.clear();
                            mShowList.addAll(mTimeList.subList(mTimeList.size() - (50 + moveTouchX), mTimeList.size() - moveTouchX));
                        }
                        postInvalidate();
                    }
                }
                startTouchX = event.getX();
                break;
            case MotionEvent.ACTION_UP:
                //手指松开
                Log.e(TAG, "onTouchEvent: 123456789->松开");
                break;
        }
        return true;
    }

下方三个动画(属性动画)也是我遇到过比较常见的

第一个是放大缩小,比如在送礼按钮上,和礼物图标上,更有冲击感
第二个是上下移动,比如某个按钮上的气泡
第三个是卡片点击左右两边时有一个缓冲效果
当然常见的还有渐变效果,比如占位图消失时,加一个渐变消失用户体验可能会更好

下面贴一下代码

可滑动分时图: TimeView

/**
 * 作者:zch
 * 时间:2023/10/10 15:08
 * 描述:自定义分时图View
 */
public class TimeView extends View {

    private Paint paint;

    private int mCanvasHeight;

    private int mCanvasWidth;

    private int mStartX = 0;

    private int mStartY;

    private int paintColor = 0;

    private int canvasColor = 0;

    private int bgColor = 0;

    private boolean isShowBg = false;

    //是否停止刷新,但是总数据一样往里面添加
    private boolean stopRe = false;
    //总数据
    private ArrayList<Integer> mTimeList = new ArrayList<>();
    //需要显示在屏幕上的数据
    private ArrayList<Integer> mShowList = new ArrayList<>();


    public TimeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    //开始绘画逻辑
    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //首先定义画笔
        paint = new Paint();
        //防锯齿
        paint.setAntiAlias(true);
        //获取画板高度
        mCanvasHeight = getMeasuredHeight();
        //获取画板宽度
        mCanvasWidth = getMeasuredWidth();
        //设置画笔颜色
        paint.setColor(paintColor == 0 ? Color.YELLOW : paintColor);
        //设置画板背景
        canvas.drawColor(canvasColor == 0 ? Color.GRAY : canvasColor);
        //设置画笔粗细
        paint.setStrokeWidth((float)3);
        //Y轴的起止都在折线图的四分之一处开始,因为数据的范围是折线图的二分之一
        //这样数据波动位置就处于折线图居中位置
        mStartY = mCanvasHeight / 4;
        //画线
        drawView(canvas);
    }
    /**
     *注释:
     *绘画逻辑
     */
    private void drawView(Canvas canvas){
        mStartX = 0;
        //如果有数据
        if (mShowList.size() > 0){
            int startX = 0,startY = 0;
            int stopX,stopY;
            for (int i = 0; i < mShowList.size(); i++){
                Integer t = mShowList.get(i);
                stopY = t+ mStartY;
                mStartX = mStartX + 10;
                //stopX = t.getStopX();
                stopX = mStartX;
                //首次的时候起始位置就等于终止位置
                if (i == 0){
                    startX = stopX;
                    startY = stopY;
                }
                //画线
                canvas.drawLine(startX,startY,stopX,stopY,paint);
                //是否画背景
                if (isShowBg){
                    setDrawBg(startX,startY,stopX,stopY,canvas);
                }
                //下次画线的时候的起始位置等于上次画线位置的终止位置
                startX = stopX;
                startY = stopY;
            }
        }
    }


    /**
     * 滑动监听
     * */
    //当前的X轴坐标,我要记录上次滑动的最后坐标,才能实时判断是否左右滑动
    private float startTouchX = 0f;
    //滑动的距离,通过他来截取对应的数据段
    private int moveTouchX = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                //手指按下
                Log.e(TAG, "onTouchEvent: 123456789->按下");
                //moveTouchX = 0;
                startTouchX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mTimeList.size() > 50){
                    stopRe = true;
                    //手指移动
                    if (startTouchX > event.getX()){
                        Log.e(TAG, "onTouchEvent: 123456789->向左滑动--" + moveTouchX);
                        if (moveTouchX > 0){
                            moveTouchX = moveTouchX - 1;
                            mShowList.clear();
                            mShowList.addAll(mTimeList.subList(mTimeList.size() - (50 + moveTouchX), mTimeList.size() - moveTouchX));
                            postInvalidate();
                        }else {
                            moveTouchX = 0;
                            stopRe = false;
                        }
                    }else {
                        Log.e(TAG, "onTouchEvent: 123456789->向右滑动---" + moveTouchX);
                        if (mTimeList.size() > (50 + moveTouchX)){
                            moveTouchX = moveTouchX + 1;
                            mShowList.clear();
                            mShowList.addAll(mTimeList.subList(mTimeList.size() - (50 + moveTouchX), mTimeList.size() - moveTouchX));
                        }
                        postInvalidate();
                    }
                }
                startTouchX = event.getX();
                break;
            case MotionEvent.ACTION_UP:
                //手指松开
                Log.e(TAG, "onTouchEvent: 123456789->松开");
                break;
        }
        return true;
    }

    /**
     *注释:
     * 画折线下背景
     */
    private void setDrawBg(int tx,int ty,int px,int py,Canvas canvas){
        //设置画笔
        Paint paint = new Paint();
        //防锯齿
        paint.setAntiAlias(true);
        //设置颜色
        paint.setColor(bgColor == 0 ? Color.WHITE : bgColor);
        //画阴影部分
        Path bg = new Path();
        bg.moveTo(tx, ty);
        bg.lineTo(tx, mCanvasHeight);
        bg.lineTo(px, mCanvasHeight);
        bg.lineTo(px, py);
        bg.lineTo(tx, ty);
        bg.close();
        //添加到画板上
        canvas.drawPath(bg, paint);
    }
    /**
     *注释:
     * 设置分数图数据,由外部传输
     */
    public void setViewData(ArrayList<Integer> m) {
        this.mTimeList = m;
        //刷新界面 - 无需在UI线程,在工作线程即可被调用,invalidate()必须在UI线程
        if (!stopRe){
            mShowList.clear();
            //只取最后50个数据
            if (mTimeList.size() > 50){
                mShowList.addAll(mTimeList.subList(mTimeList.size()-50, mTimeList.size() - 1));
            }else {
                mShowList.addAll(mTimeList);
            }
            postInvalidate();
        }
    }
    /**
     *注释:
     *设置分时图背景和画笔颜色
     * p - 画笔颜色
     * c - 背景颜色
     * b - 折线下背景颜色
     */
    public void setViewColor(int p,int c,int b){
        this.paintColor = p;
        this.canvasColor = c;
        this.bgColor = b;
    }
    /**
     *注释:
     * 是否显示折线下的背景
     */
    public void setShowBgBottom(boolean b){
        this.isShowBg = b;
    }


}

使用:
这里用定时来动态获取数据,其中 initData 为请求数据,这里用的模拟数据

    /**
     * 通过线程来动态设置View达到动画效果
     */
    private val thread: Thread = object : Thread() {
        @RequiresApi(api = Build.VERSION_CODES.N)
        override fun run() {
            while (true) {
                try {

                    initData()

                    //如果数据终止X的值大于界面宽度停止线程或者其他操作
                    /*if (mTimeList.size > 0 && mTimeList.get(mTimeList.size - 1).stopX > tvTime!!.measuredWidth) {
                        //清除容器数据,重新添加
                        startX = 0
                        mTimeList.clear()
                        //thread.stop(); //终止线程
                    }*/
                    /**休息时间 */
                    sleep(1000)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }
        }
    }
    private val mTimeList: ArrayList<Int> = ArrayList()
    @RequiresApi(Build.VERSION_CODES.N)
    private fun initData() {

        //设置Y轴数据的终止位置,无需设置起始位置,因为每条线的起始位置就是上一条线的终点位置
        val ra = Random()
        val stopY: Int = ra.nextInt(40)

        //给分时图设置数据
        mTimeList.add(stopY)
        tvTime?.setViewData(mTimeList)
    }

缩放动画:
这里分两部分,先缩,监听缩结束后去放,如此反复

    //缩,将View传进来即可
    fun showS1(view: View) {
        val scaleAnimation = ScaleAnimation(
            1f, 0.5f,
            1f, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f
        )
        scaleAnimation.setAnimationListener(object : Animation.AnimationListener {
            override fun onAnimationStart(animation: Animation) {}
            override fun onAnimationEnd(animation: Animation) {
                showF1(view)
            }

            override fun onAnimationRepeat(animation: Animation) {}
        })
        scaleAnimation.duration = 300
        scaleAnimation.fillAfter = true
        view.startAnimation(scaleAnimation)
    }
    //放
    fun showF1(view: View) {
        val scaleAnimation = ScaleAnimation(
            0.5f, 1f,
            0.5f, 1f,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f
        )
        scaleAnimation.duration = 300
        scaleAnimation.setAnimationListener(object : Animation.AnimationListener {
            override fun onAnimationStart(animation: Animation) {}
            override fun onAnimationEnd(animation: Animation) {
                view.clearAnimation()
                showS1(view)
            }

            override fun onAnimationRepeat(animation: Animation) {}
        })
        scaleAnimation.fillAfter = true
        view.startAnimation(scaleAnimation)
    }

上下动画

    fun sx(){
        val view = findViewById<TextView>(R.id.tv_vip1)
        val upAnim = ObjectAnimator.ofFloat(view, "translationY", 0F, -50F, 0F)
        upAnim.duration = 2000
        upAnim.interpolator = LinearInterpolator()
        upAnim.addListener(object :AnimatorListenerAdapter(){
            override fun onAnimationEnd(animation: Animator?) {
                super.onAnimationEnd(animation)
                //动画结束
            }
        })
        upAnim.repeatCount = ValueAnimator.INFINITE;//无限循环
        //启动
        upAnim.start()
    }

卡片倾斜动画

    /**
     * 倾斜动画 f- 倾斜角度,这里建议 30-50*/
    fun showQX(view: View, f: Float?) {
        val valueAnimator = ValueAnimator.ofFloat(0f, f!!)
        valueAnimator.addUpdateListener { animation ->
            val deltaY = animation.animatedValue as Float
            view.rotationY = deltaY
        }
        valueAnimator.addListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(animator: Animator) {}
            override fun onAnimationEnd(animator: Animator) {
                showHuiFu(view, f)
            }

            override fun onAnimationCancel(animator: Animator) {}
            override fun onAnimationRepeat(animator: Animator) {}
        })
        //默认duration是300毫秒
        valueAnimator.duration = 300
        valueAnimator.start()
    }

    /**
     * 恢复倾斜动画 */
    fun showHuiFu(view: View, f: Float?) {
        val valueAnimator = ValueAnimator.ofFloat(f!!, 0f)
        valueAnimator.addUpdateListener { animation ->
            val deltaY = animation.animatedValue as Float
            view.rotationY = deltaY
        }

        //默认duration是300毫秒
        valueAnimator.duration = 300
        valueAnimator.start()
    }

你可能感兴趣的:(Android,android,Kotlin写法)