Android 自定义View时间任务节点

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Toast;

import com.iec.hydropath10.R;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class TimeRulerViewV3 extends View {

    private List<Long> timeNodes; // 时间节点集合
    private static final int TICK_HEIGHT_MAJOR = 40;
    private static final int TICK_HEIGHT_MINOR = 20;
    private static final int TEXT_SIZE = 30;
    private static final int BORDER_WIDTH = 5;

    private int tickIntervalMinutes=0;

    private static final long MINIMUM_TIME_RANGE = 15 * 60 * 1000; // 15 minutes

    private Date startTime;
    private Date endTime;
    private float lastX;
    private long currentTime;

    private int scaleRulerColor;//刻度尺
    private int scaleMarkColor;//刻度标

    private int hourColor;//时针颜色

    private int MINUTECOLOR;//分针颜色;

    private int RulerColor; //尺身边框颜色

    private Bitmap bitmap; //摄像头
    /**
     * 动画部分
     */
    private ObjectAnimator animator;
    private Paint paint;
    private float translationX;


    public void setRulerColor(int rulerColor) {
        RulerColor = rulerColor;
    }

    public void setMINUTECOLOR(int MINUTECOLOR) {
        this.MINUTECOLOR = MINUTECOLOR;
    }

    public void setHourColor(int hourColor) {
        this.hourColor = hourColor;
    }

    public void setScaleRulerColor(int scaleRulerColor) {
        this.scaleRulerColor = scaleRulerColor;
    }

    public void setScaleMarkColor(int scaleMarkColor) {
        this.scaleMarkColor = scaleMarkColor;
    }

    private ScaleGestureDetector scaleGestureDetector;

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

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

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

    private void init() {
        paint = new Paint();
        paint.setAntiAlias(true); // 启用抗锯齿
        paint.setTextSize(TEXT_SIZE);
        paint.setTextAlign(Paint.Align.LEFT);
        currentTime = System.currentTimeMillis();
        startTime = new Date(currentTime);
        endTime = new Date(currentTime + 3 * 60 * 60 * 1000); // 3 hours from now
        scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
        animator = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f);
        animator.setDuration(1500); // 动画持续时间为1秒
        animator.setInterpolator(new AccelerateDecelerateInterpolator());
        timeNodes = new ArrayList<>();

        // 启动动画
        animator.start();

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 5; // 缩小为原始尺寸的 1/2
        bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.camer, options); // 替换为你的图片资源


        post(new Runnable() {
            @Override
            public void run() {
                startAnimation();
            }
        });

    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
        invalidate();
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
        invalidate();
    }

    public void setTickIntervalMinutes(int tickIntervalMinutes) {
           this.tickIntervalMinutes=tickIntervalMinutes;
    }

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

        int viewWidth = getWidth();
        int viewHeight = getHeight();

        int usableWidth = viewWidth - BORDER_WIDTH;

        int totalMinutes = (int) ((endTime.getTime() - startTime.getTime()) / (1000 * 60));
        float pixelsPerMinute = (float) usableWidth / totalMinutes;
        canvas.drawBitmap(bitmap, translationX, 50, paint);
        Paint paint = new Paint();

        paint.setTextSize(TEXT_SIZE);
        paint.setTextAlign(Paint.Align.CENTER);

        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());

        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(BORDER_WIDTH);
        paint.setColor(RulerColor);
        canvas.drawRect(BORDER_WIDTH / 2f, BORDER_WIDTH / 2f, viewWidth - BORDER_WIDTH / 2f, getHeight()-100, paint);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(startTime);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);


        while (calendar.getTime().before(endTime)) {
            int minutesFromStart = (int) ((calendar.getTime().getTime() - startTime.getTime()) / (1000 * 60));
            int x = (int) (minutesFromStart * pixelsPerMinute);
            x += BORDER_WIDTH / 2;
            boolean isMajorTick = calendar.get(Calendar.MINUTE) == 0;
            int num = calendar.get(Calendar.HOUR);
            int tickHeight = isMajorTick ? TICK_HEIGHT_MAJOR : TICK_HEIGHT_MINOR;
            if (x >= BORDER_WIDTH / 2 && x <= viewWidth - BORDER_WIDTH / 2) {
                paint.setColor(scaleMarkColor);

                if (isMajorTick) {
                    paint.setColor(hourColor);
                    paint.setStrokeWidth(2);
                    canvas.drawLine(x, getHeight()-100, x, 0, paint);
                    String timeText = sdf.format(calendar.getTime());
                    paint.setColor(scaleRulerColor);
                    paint.setStrokeWidth(5);
                    canvas.drawText(timeText, x, viewHeight - tickHeight - 10, paint);
                }else {
                    paint.setColor(MINUTECOLOR);
                    paint.setStrokeWidth(2);
                    canvas.drawLine(x, getHeight()-100, x, getHeight()-120, paint);
                }
            }
            calendar.add(Calendar.MINUTE, tickIntervalMinutes);
        }

        // 绘制时间节点图表
        drawChartAtTimeNodes(canvas);
    }


    private long lastClickTime = 0;
    private float startX; // 记录手指按下时的x坐标
    private float startY; // 记录手指按下时的y坐标
    private boolean isMoving = false; // 记录是否正在滑动
    private static final long DOUBLE_CLICK_TIME_DELTA = 300; // 双击间隔时间(以毫秒为单位)
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        scaleGestureDetector.onTouchEvent(event);
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
                lastX = event.getX();
                startX = event.getX();
                startY = event.getY();
                isMoving = false; // 每次按下时重置isMoving状态
                lastClickTime = System.currentTimeMillis();

                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs(event.getX() - startX) > 10 || Math.abs(event.getY() - startY) > 10) {
                    isMoving = true;
                }
                if (!scaleGestureDetector.isInProgress()) {
                    if (event.getPointerCount() == 1) {


                        if (startTime.getTime() < currentTime) {
                            // 如果开始时间已经是最初的开始时间,则禁止滑动
                            startTime.setTime(currentTime);
                            break;
                        }
                        float currentX = event.getX();
                        float deltaX = currentX - lastX;
                        lastX = currentX;

                        // 计算时间偏移量
                        long timeOffset = (long) (-deltaX / getWidth() * (endTime.getTime() - startTime.getTime()));

                        // 更新开始时间和结束时间
                        startTime.setTime(startTime.getTime() + timeOffset);
                        endTime.setTime(endTime.getTime() + timeOffset);
                    // 单指滑动

                    invalidate();
                }else if (event.getPointerCount() == 2){
                        float currentX = event.getX();
                        float deltaX = currentX - lastX;
                        lastX = currentX;

                        if (deltaX > 0) {
                            // 向右滑动
                            if (startTime.getTime() == currentTime) {
                                // 如果开始时间已经是最初的开始时间,则禁止滑动
                                break;
                            }
                            long timeOffset = (long) (-deltaX / getWidth() * (endTime.getTime() - startTime.getTime()));
                            long newStartTime = startTime.getTime() + timeOffset;

                            if (newStartTime <= currentTime) {
                                startTime.setTime(currentTime);
                                endTime.setTime(currentTime + endTime.getTime() - startTime.getTime());
                            } else {
                                startTime.setTime(newStartTime);
                            }
                        } else if (deltaX < 0) {
                            // 向左滑动
                            long timeOffset = (long) (-deltaX / getWidth() * (endTime.getTime() - startTime.getTime()));
                            long newEndTime = endTime.getTime() + timeOffset;

                            if (newEndTime <= currentTime) {
                                endTime.setTime(currentTime);
                                startTime.setTime(currentTime - (endTime.getTime() - startTime.getTime()));
                            } else {
                                startTime.setTime(startTime.getTime() + timeOffset);
                                endTime.setTime(newEndTime);
                            }
                        }

                        // 重新绘制视图
                        invalidate();
                }
        }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                long clickTime = System.currentTimeMillis();
                float x = event.getX();
                float y = event.getY();
                // 检查点击位置是否在时间节点附近
                if (clickTime - lastClickTime < DOUBLE_CLICK_TIME_DELTA) {
                    // 双击事件

                } else {
                    if (!isMoving) {
                        checkTimeNodeClicked(x, y);
                    }

                }
                break;
        }

        return true;
    }

    // 检查点击位置是否在时间节点附近
    private void checkTimeNodeClicked(float x, float y) {
        // 计算点击位置对应的时间
        int totalMinutes = (int) ((endTime.getTime() - startTime.getTime()) / (1000 * 60));
        float pixelsPerMinute = (float) getWidth() / totalMinutes;
        long clickedTime = startTime.getTime() + (long) ((x / getWidth()) * totalMinutes * 60 * 1000);

        // 遍历时间节点,查找是否有点击的时间节点
        boolean timeNodeFound = false;
        for (Long timeNode : timeNodes) {
            long difference = Math.abs(timeNode - clickedTime);
            if (difference <= 15 * 60 * 1000) { // 15 minutes
                // 计算时间节点对应的 x 坐标
                int minutesFromStart = (int) ((timeNode - startTime.getTime()) / (1000 * 60));
                int xCoordinate = (int) (minutesFromStart * pixelsPerMinute);

                // 判断点击位置是否在时间节点附近
                if (Math.abs(x - xCoordinate) <= 60 && Math.abs(y - getHeight() / 2) <= 60) {
                    // 点击到了时间节点,执行相应的操作,例如显示提示信息等
                    // 此处可以根据需要进行处理
                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
                    String currentTimeString = sdf.format(timeNode);
                    showToast("任务节点:"+currentTimeString);
                    timeNodeFound = true;
                    break;
                }
            }
        }

        // 如果没有点击到时间节点,则显示当前时间
        // 如果没有点击到时间节点,则显示时间轴上点击位置对应的时间
        if (!timeNodeFound) {
            // 将点击位置对应的时间转换为 Date 对象
            Date clickedDate = new Date(clickedTime);

            // 格式化时间
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
            String clickedTimeString = sdf.format(clickedDate);

            // 显示时间轴上点击位置对应的时间
            showToast("创建任务时间:" + clickedTimeString);
        }

    }



    // 显示提示信息
    private void showToast(String message) {
        Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
    }
    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float scaleFactor = detector.getScaleFactor();

            long totalMilliseconds = endTime.getTime() - startTime.getTime();
            long scaledMilliseconds = (long) (totalMilliseconds / scaleFactor);

            if (scaledMilliseconds < MINIMUM_TIME_RANGE) {
                scaledMilliseconds = MINIMUM_TIME_RANGE;
            }

            long newEndTimeMilliseconds = startTime.getTime() + scaledMilliseconds;
            endTime.setTime(newEndTimeMilliseconds);

            invalidate();
            return true;
        }
    }

    private void startAnimation() {
        // 创建值动画,从左边界到0
        ValueAnimator animator = ValueAnimator.ofFloat(0, getWidth()-60);
        animator.setDuration(5000); // 动画持续时间为1秒
        animator.setInterpolator(new AccelerateDecelerateInterpolator());

        // 添加动画监听器,在动画更新时更新translationX的值
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                translationX = (float) animation.getAnimatedValue();
                invalidate(); // 重绘视图
            }
        });

        // 启动动画
        animator.start();
    }


    // 设置时间节点集合
    public void setTimeNodes(List<Long> timeNodes) {
        this.timeNodes = timeNodes;
        invalidate();
    }

    // 绘制时间节点图表
    private void drawChartAtTimeNodes(Canvas canvas) {
        for (Long timeNode : timeNodes) {
            drawChartAtSpecificTime(canvas, timeNode);
        }
    }

    // 绘制特定时间的图表
    private void drawChartAtSpecificTime(Canvas canvas, long specificTimeInMillis) {
        // 检查特定时间是否在可见范围内
        if (specificTimeInMillis < startTime.getTime() || specificTimeInMillis > endTime.getTime()) {
            return;
        }

        // 计算特定时间对应的 x 坐标
        int totalMinutes = (int) ((endTime.getTime() - startTime.getTime()) / (1000 * 60));
        int minutesFromStart = (int) ((specificTimeInMillis - startTime.getTime()) / (1000 * 60));
        float pixelsPerMinute = (float) getWidth() / totalMinutes;
        int x = (int) (minutesFromStart * pixelsPerMinute);

        // 假设您要绘制一个圆形图表
        Paint paint = new Paint();
        paint.setTextSize(30);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.FILL);
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.getDefault());
        String timeText = sdf.format(specificTimeInMillis);
        canvas.drawText(""+timeText, x, getHeight()/2, paint);
    }
}

支持多种手势操作时间控件,可自行改进满足业务需求

相关推荐

  1. Android基础-定义view

    2024-03-15 15:38:05       28 阅读
  2. Android定义View保存为Bitmap图片

    2024-03-15 15:38:05       50 阅读
  3. Android开发中定义View实现RecyclerView下划线

    2024-03-15 15:38:05       42 阅读
  4. 定义View

    2024-03-15 15:38:05       49 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-15 15:38:05       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-15 15:38:05       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-15 15:38:05       82 阅读
  4. Python语言-面向对象

    2024-03-15 15:38:05       91 阅读

热门阅读

  1. int8量化和int16量化的区别

    2024-03-15 15:38:05       45 阅读
  2. 力扣题库第10题:和为K的子数组

    2024-03-15 15:38:05       40 阅读
  3. 在Odoo中定义基于SQL视图的模型

    2024-03-15 15:38:05       50 阅读
  4. 【yolo检测模型出现大量误报】

    2024-03-15 15:38:05       46 阅读
  5. 3月14日,每日信息差

    2024-03-15 15:38:05       40 阅读
  6. Leetcode 287. 寻找重复数

    2024-03-15 15:38:05       44 阅读
  7. MySQL Joins 学习笔记

    2024-03-15 15:38:05       53 阅读
  8. Oracle EMCC数据库集中管理平台安装配置与使用

    2024-03-15 15:38:05       45 阅读
  9. 力扣_动态规划2—乘积最大的子数组

    2024-03-15 15:38:05       45 阅读
  10. TextView 中实现打印效果并且可以换行

    2024-03-15 15:38:05       44 阅读
  11. leetcode257.二叉树的所有路径

    2024-03-15 15:38:05       42 阅读
  12. 【25届秋招备战C++】算法篇-贪心算法(Greedy)

    2024-03-15 15:38:05       48 阅读
  13. 八数码(A*算法)+单词接龙(DFS)

    2024-03-15 15:38:05       41 阅读