如何编写旋转的圆形动画,例如这个(附图片)? Android的

时间:2013-01-25 19:28:48

标签: android animation graphics android-animation

我正在开发一个app&刚建立其逻辑部分。现在我想设计这个应用程序,就像在着名的计时器apps.for例如:

http://goo.gl/N3qS1
enter image description here

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

2 个答案:

答案 0 :(得分:6)

这会吗?

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

示例屏幕截图: Sample screenshot

<强>代码:

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);
        }

    }

}

答案 1 :(得分:0)

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();
}