11

我正在开发一个应用程序,只是构建了它的逻辑部分。现在我想设计这个应用程序,就像著名的计时器应用程序一样。例如:

http://goo.gl/N3qS1
在此处输入图像描述

我想要的是外圈,它充满了某个事件的每个触发器或数字的增加。我实际上不知道它是动画部分(比如内置在 Flash 中或什么),或者只是通过使用其内置属性和功能在 android 本身中编码来实现。因此,如果有人告诉我,请向我解释使用了哪些工具或任何可以从底层解释事情的参考教程。我真的不懂设计。这有什么代码吗??

4

2 回答 2

6

这会吗?

更新:现在也正确处理现实世界的时间。

示例截图: 示例截图

代码:

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextPaint;
import android.view.View;

import android.graphics.*;


public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(new CircularCountdown(this));
    }

    private static class CircularCountdown extends View {

        private final Paint backgroundPaint;
        private final Paint progressPaint;
        private final Paint textPaint;

        private long startTime;
        private long currentTime;
        private long maxTime;

        private long progressMillisecond;
        private double progress;

        private RectF circleBounds;
        private float radius;
        private float handleRadius;
        private float textHeight;
        private float textOffset;

        private final Handler viewHandler;
        private final Runnable updateView;

        public CircularCountdown(Context context) {
            super(context);

            // used to fit the circle into
            circleBounds = new RectF();

            // size of circle and handle
            radius = 200;
            handleRadius = 10;

            // limit the counter to go up to maxTime ms
            maxTime = 5000;

            // start and current time
            startTime = System.currentTimeMillis();
            currentTime = startTime;


            // the style of the background
            backgroundPaint = new Paint();
            backgroundPaint.setStyle(Paint.Style.STROKE);
            backgroundPaint.setAntiAlias(true);
            backgroundPaint.setStrokeWidth(10);
            backgroundPaint.setStrokeCap(Paint.Cap.SQUARE);
            backgroundPaint.setColor(Color.parseColor("#4D4D4D"));  // dark gray

            // the style of the 'progress'
            progressPaint = new Paint();
            progressPaint.setStyle(Paint.Style.STROKE);
            progressPaint.setAntiAlias(true);
            progressPaint.setStrokeWidth(10);
            progressPaint.setStrokeCap(Paint.Cap.SQUARE);
            progressPaint.setColor(Color.parseColor("#00A9FF"));    // light blue

            // the style for the text in the middle
            textPaint = new TextPaint();
            textPaint.setTextSize(radius / 2);
            textPaint.setColor(Color.BLACK);
            textPaint.setTextAlign(Paint.Align.CENTER);

            // text attributes
            textHeight = textPaint.descent() - textPaint.ascent();
            textOffset = (textHeight / 2) - textPaint.descent();


            // This will ensure the animation will run periodically
            viewHandler = new Handler();
            updateView = new Runnable(){
                @Override
                public void run(){
                    // update current time
                    currentTime = System.currentTimeMillis();

                    // get elapsed time in milliseconds and clamp between <0, maxTime>
                    progressMillisecond = (currentTime - startTime) % maxTime;

                    // get current progress on a range <0, 1>
                    progress = (double) progressMillisecond / maxTime;

                    CircularCountdown.this.invalidate();
                    viewHandler.postDelayed(updateView, 1000/60);
                }
            };
            viewHandler.post(updateView);
        }



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

            // get the center of the view
            float centerWidth = canvas.getWidth() / 2;
            float centerHeight = canvas.getHeight() / 2;


            // set bound of our circle in the middle of the view
            circleBounds.set(centerWidth - radius,
                    centerHeight - radius,
                    centerWidth + radius,
                    centerHeight + radius);


            // draw background circle
            canvas.drawCircle(centerWidth, centerHeight, radius, backgroundPaint);

            // we want to start at -90°, 0° is pointing to the right
            canvas.drawArc(circleBounds, -90, (float)(progress*360), false, progressPaint);

            // display text inside the circle
            canvas.drawText((double)(progressMillisecond/100)/10 + "s",
                            centerWidth,
                            centerHeight + textOffset,
                            textPaint);

            // draw handle or the circle
            canvas.drawCircle((float)(centerWidth  + (Math.sin(progress * 2 * Math.PI) * radius)),
                              (float)(centerHeight - (Math.cos(progress * 2 * Math.PI) * radius)),
                              handleRadius,
                              progressPaint);
        }

    }

}
于 2014-12-04T11:38:07.403 回答
1

的解决方案Antimonit有两个重大问题:

  1. circular clock view当您使用 破坏活动/片段并再次显示时钟时,会发生内存泄漏。
  2. 所有参数都在 java 类中硬编码,循环时钟视图类不可重用。

基于Antimonit代码(谢谢!),我创建了更多可重用和内存安全的解决方案。现在,几乎所有的参数都可以从XML文件中设置。在活动/片段类的最后,我们需要调用startCount方法。removeCallbacks当活动/片段将被销毁以避免内存泄漏时,我强烈推荐调用方法。

KakaCircularCounter.java 类:

public class KakaCircularCounter extends View {
public static final int DEF_VALUE_RADIUS = 250;
public static final int DEF_VALUE_EDGE_WIDTH = 15;
public static final int DEF_VALUE_TEXT_SIZE = 18;
private Paint backgroundPaint;
private Paint progressPaint;
private Paint textPaint;
private RectF circleBounds;
private long startTime;
private long currentTime;
private long maxTime;
private long progressMillisecond;
private double progress;
private float radius;
private float edgeHeadRadius;
private float textInsideOffset;
private KakaDirectionCount countDirection;
private Handler viewHandler;
private Runnable updateView;

public KakaCircularCounter(Context context) {
    super(context);
    init(null);
}

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

public KakaCircularCounter(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(attrs);
}

public KakaCircularCounter(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init(attrs);
}

private void init(AttributeSet attrSet) {
    if (attrSet == null) {
        return;
    }

    TypedArray typedArray = getContext().obtainStyledAttributes(attrSet, R.styleable.KakaCircularCounter);

    circleBounds = new RectF();
    backgroundPaint = setupBackground(typedArray);
    progressPaint = setupProgress(typedArray);
    textPaint = setupText(typedArray);

    textInsideOffset = (textPaint.descent() - textPaint.ascent() / 2) - textPaint.descent();

    radius = typedArray.getDimensionPixelSize(R.styleable.KakaCircularCounter_clockRadius, DEF_VALUE_RADIUS);
    edgeHeadRadius = typedArray.getDimensionPixelSize(R.styleable.KakaCircularCounter_edgeHeadRadius, DEF_VALUE_EDGE_WIDTH);

    countDirection = KakaDirectionCount.values()[typedArray.getInt(R.styleable.KakaCircularCounter_countFrom,
            KakaDirectionCount.MAXIMUM.ordinal())];

    typedArray.recycle();
}

private Paint setupText(TypedArray typedArray) {
    Paint t = new Paint();
    t.setTextSize(typedArray.getDimensionPixelSize(R.styleable.KakaCircularCounter_textInsideSize, DEF_VALUE_TEXT_SIZE));
    t.setColor(typedArray.getColor(R.styleable.KakaCircularCounter_textInsideColor, Color.BLACK));
    t.setTextAlign(Paint.Align.CENTER);
    return t;
}

private Paint setupProgress(TypedArray typedArray) {
    Paint p = new Paint();
    p.setStyle(Paint.Style.STROKE);
    p.setAntiAlias(true);
    p.setStrokeCap(Paint.Cap.SQUARE);
    p.setStrokeWidth(typedArray.getDimensionPixelSize(R.styleable.KakaCircularCounter_clockWidth, DEF_VALUE_EDGE_WIDTH));
    p.setColor(typedArray.getColor(R.styleable.KakaCircularCounter_edgeBackground, Color.parseColor("#4D4D4D")));
    return p;
}

private Paint setupBackground(TypedArray ta) {
    Paint b = new Paint();
    b.setStyle(Paint.Style.STROKE);
    b.setStrokeWidth(ta.getDimensionPixelSize(R.styleable.KakaCircularCounter_clockWidth, DEF_VALUE_EDGE_WIDTH));
    b.setColor(ta.getColor(R.styleable.KakaCircularCounter_clockBackground, Color.parseColor("#4D4D4D")));
    b.setAntiAlias(true);
    b.setStrokeCap(Paint.Cap.SQUARE);
    return b;
}


public void startCount(long maxTimeInMs) {
    startTime = System.currentTimeMillis();
    this.maxTime = maxTimeInMs;

    viewHandler = new Handler();
    updateView = () -> {
        currentTime = System.currentTimeMillis();
        progressMillisecond = (currentTime - startTime) % maxTime;
        progress = (double) progressMillisecond / maxTime;
        KakaCircularCounter.this.invalidate();
        viewHandler.postDelayed(updateView, 1000 / 60);
    };
    viewHandler.post(updateView);
}

public void removeCallbacks() {
    viewHandler.removeCallbacks(updateView);
}


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    float centerWidth = getWidth() / 2f;
    float centerHeight = getHeight() / 2f;

    circleBounds.set(centerWidth - radius,
            centerHeight - radius,
            centerWidth + radius,
            centerHeight + radius);

    canvas.drawCircle(centerWidth, centerHeight, radius, backgroundPaint);
    canvas.drawArc(circleBounds, -90, (float) (progress * 360), false, progressPaint);

    canvas.drawText(getTextToDraw(),
            centerWidth,
            centerHeight + textInsideOffset,
            textPaint);

    canvas.drawCircle((float) (centerWidth + (Math.sin(progress * 2 * Math.PI) * radius)),
            (float) (centerHeight - (Math.cos(progress * 2 * Math.PI) * radius)),
            edgeHeadRadius,
            progressPaint);
}

@NonNull
private String getTextToDraw() {
    if (countDirection.equals(KakaDirectionCount.ZERO)) {
        return String.valueOf(progressMillisecond / 1000);
    } else {
        return String.valueOf((maxTime - progressMillisecond) / 1000);
    }
}

}

KakaDirectionCount 枚举:

public enum KakaDirectionCount {
    ZERO, MAXIMUM
}

值目录中的属性文件 (kaka_circular_counter.xml)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="KakaCircularCounter">
        <attr name="clockRadius" format="dimension"/>
        <attr name="clockBackground" format="color"/>
        <attr name="clockWidth" format="dimension"/>
        <attr name="edgeBackground" format="color"/>
        <attr name="edgeWidth" format="dimension"/>
        <attr name="edgeHeadRadius" format="dimension"/>
        <attr name="textInsideSize" format="dimension"/>
        <attr name="textInsideColor" format="color"/>
        <attr name="countFrom" format="enum">
            <enum name="ZERO" value="0"/>
            <enum name="MAXIMUM" value="1"/>
        </attr>
    </declare-styleable>
</resources>

在 xml 文件中使用的示例:

    <pl.kaka.KakaCircularCounter
        android:id="@+id/circular_counter"
        android:layout_width="180dp"
        android:layout_height="180dp"
        app:layout_constraintBottom_toBottomOf="@id/backgroundTriangle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/backgroundTriangle"
        app:clockRadius="85dp"
        app:clockBackground="@color/colorTransparent"
        app:clockWidth="3dp"
        app:edgeBackground="@color/colorAccentSecondary"
        app:edgeWidth="5dp"
        app:edgeHeadRadius="1dp"
        app:textInsideSize="60sp"
        app:textInsideColor="@color/colorWhite"
        app:countFrom="MAXIMUM"/>

在活动或片段中使用的示例:

//the number in parameter is the value of the counted time
binding.circularCounter.startCount(12000);

注意:记得在销毁活动/片段时删除回调,因为发生内存泄漏。例如:

@Override
public void onDestroyView() {
    super.onDestroyView();
    binding.circularCounter.removeCallbacks();
}
于 2019-04-04T13:54:17.577 回答