Как я могу запрограммировать вращающуюся круговую анимацию, такую как эта (прикрепленная фотография)? Android
Я разрабатываю приложение и просто создаю его логическую часть. Теперь я хочу создать это приложение, как в знаменитых таймерных приложениях. Примеры:
![http://goo.gl/N3qS1]()
![enter image description here]()
То, что я хочу, это внешний круг, который заполняется каждым триггером какого-либо события или с увеличением числа. На самом деле я не знаю, что это анимационная часть (например, встроенная в флеш-память или что) или просто возможно с помощью кодирования в андроиде с использованием встроенных свойств и функций.
Поэтому, если кто-нибудь скажет мне, какие инструменты используются или какой-либо справочный учебник, который может объяснить вещи снизу. Я действительно ничего не знаю о дизайне. Любой код для этого??
Ответы
Ответ 1
Будет ли это делать?
Обновление: теперь также правильно обрабатывается реальное время.
Пример скриншота: ![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);
}
}
}
Ответ 2
http://www.sarahjustine.com/edge/
Сделайте это в Adobe edge и экспортируйте его в phonegap. Постройте его с разрывом в телефоне, чтобы добавить источники в ваш sdk.
Ответ 3
В андроиде можно определить анимацию, но вам придется "вырезать" анимацию на части и собрать ее во время работы в Android.
http://digitaldumptruck.jotabout.com/?p=813 это должна быть статья, которую вы ищете;)
Ответ 4
Нам нужно настроить индикатор выполнения с анимацией. Следуйте ссылкам для проекта библиотеки,
https://github.com/Todd-Davies/ProgressWheel
https://github.com/f2prateek/progressbutton
См. Следующую ссылку для учебников.
http://learnandroideasily.blogspot.in/2013/05/custom-progress-bar-in-android.html
Ответ 5
Решение 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 enum:
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();
}