Androidには、不確定のときに特別なアニメーションを表示する標準のProgressBarがあります。利用可能な非常に多くの種類の進行状況ビューのライブラリもたくさんあります( here )。
私が検索したすべての中で、非常に単純なことを行う方法を見つけることができません。
色Xから色Yへのグラデーションがあり、水平に表示され、X座標で移動して、Xの前の色が色Yになるようにします。
たとえば(単なるイラスト)、EdgeからEdgeへのblue <-> redのグラデーションがある場合、次のようになります。
ここで提供されているいくつかのソリューションをStackOverflowで試しました:
悲しいことに、これらはすべてAndroidの標準のProgressBarビューに関するものです。つまり、ドローアブルのアニメーションを表示する方法が異なります。
Androidアーセナルのウェブサイトでも似たようなものを見つけようとしましたが、ニースのサイトはたくさんありますが、そのようなものは見つかりませんでした。
もちろん、自分で2つのビューをアニメーション化することもできます。それぞれに独自の勾配があります(一方が他方の反対です)が、もっと良い方法があると確信しています。
Drawableまたはそのアニメーションを使用して、グラデーション(またはその他)をこのように移動させることは可能ですか(もちろん繰り返します)?
たぶんImageViewから拡張して、そこでドローアブルをアニメーション化するだけでしょうか?
繰り返しドローアブルに使用されるコンテナの量を設定することもできますか?つまり、上記の例では、青から赤になり、青が端になり、赤が中央になります。
編集:
OK、少し進歩しましたが、動きが大丈夫かどうかはわかりません。CPUが少しビジーな場合に備えて、速度が適切に一定していないと思います。フレームドロップは考慮されません。私がしたことは、次のように2つのGradientDrawableを隣同士に描画することです。
class HorizontalProgressView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val speedInPercentage = 1.5f
private var xMovement: Float = 0.0f
private val rightDrawable: GradientDrawable = GradientDrawable()
private val leftDrawable: GradientDrawable = GradientDrawable()
init {
if (isInEditMode)
setGradientColors(intArrayOf(Color.RED, Color.BLUE))
rightDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT;
rightDrawable.orientation = GradientDrawable.Orientation.LEFT_RIGHT
rightDrawable.shape = GradientDrawable.RECTANGLE;
leftDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT;
leftDrawable.orientation = GradientDrawable.Orientation.RIGHT_LEFT
leftDrawable.shape = GradientDrawable.RECTANGLE;
}
fun setGradientColors(colors: IntArray) {
rightDrawable.colors = colors
leftDrawable.colors = colors
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)
val heightSize = View.MeasureSpec.getSize(heightMeasureSpec)
rightDrawable.setBounds(0, 0, widthSize, heightSize)
leftDrawable.setBounds(0, 0, widthSize, heightSize)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.save()
if (xMovement < width) {
canvas.translate(xMovement, 0.0f)
rightDrawable.draw(canvas)
canvas.translate(-width.toFloat(), 0.0f)
leftDrawable.draw(canvas)
} else {
//now the left one is actually on the right
canvas.translate(xMovement - width, 0.0f)
leftDrawable.draw(canvas)
canvas.translate(-width.toFloat(), 0.0f)
rightDrawable.draw(canvas)
}
canvas.restore()
xMovement += speedInPercentage * width / 100.0f
if (isInEditMode)
return
if (xMovement >= width * 2.0f)
xMovement = 0.0f
invalidate()
}
}
使用法:
horizontalProgressView.setGradientColors(intArrayOf(Color.RED, Color.BLUE))
そして結果(ループはうまくいきます、ビデオを編集するのは難しいです):
だから私の質問は、UIスレッドが少し忙しい場合でも、アニメーションがうまく機能することを確認するにはどうすればよいですか?
invalidate
だけでは信頼できる方法とは思えないだけです。それ以上チェックすべきだと思います。多分それは代わりにいくつかのアニメーションAPIを使用することができます。
ここでKotlinに「pskink」という回答を入れることにしました(Origin here )。ここに書いたのは、他の解決策が機能しなかった、または私が尋ねたものの代わりに回避策だったからです。
class ScrollingGradient(private val pixelsPerSecond: Float) : Drawable(), Animatable, TimeAnimator.TimeListener {
private val Paint = Paint()
private var x: Float = 0.toFloat()
private val animator = TimeAnimator()
init {
animator.setTimeListener(this)
}
override fun onBoundsChange(bounds: Rect) {
Paint.shader = LinearGradient(0f, 0f, bounds.width().toFloat(), 0f, Color.WHITE, Color.BLUE, Shader.TileMode.MIRROR)
}
override fun draw(canvas: Canvas) {
canvas.clipRect(bounds)
canvas.translate(x, 0f)
canvas.drawPaint(Paint)
}
override fun setAlpha(alpha: Int) {}
override fun setColorFilter(colorFilter: ColorFilter?) {}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
override fun start() {
animator.start()
}
override fun stop() {
animator.cancel()
}
override fun isRunning(): Boolean = animator.isRunning
override fun onTimeUpdate(animation: TimeAnimator, totalTime: Long, deltaTime: Long) {
x = pixelsPerSecond * totalTime / 1000
invalidateSelf()
}
}
使用法:
MainActivity.kt
val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200f, resources.getDisplayMetrics())
progress.indeterminateDrawable = ScrollingGradient(px)
activity_main.xml
<LinearLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android" xmlns:app="http://schemas.Android.com/apk/res-auto"
xmlns:tools="http://schemas.Android.com/tools" Android:layout_width="match_parent"
Android:layout_height="match_parent" Android:gravity="center" Android:orientation="vertical"
tools:context=".MainActivity">
<ProgressBar
Android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" Android:layout_width="200dp"
Android:layout_height="20dp" Android:indeterminate="true"/>
</LinearLayout>
私のソリューションの背後にあるアイデアは比較的単純です。2つの子ビュー(開始-終了グラデーションと終了-開始グラデーション)があるFrameLayout
を表示し、ValueAnimator
を使用して子ビューをアニメーション化します ' translationX
属性。カスタム描画を行っておらず、フレームワークが提供するアニメーションユーティリティを使用しているので、アニメーションのパフォーマンスについて心配する必要はありません。
これをすべて管理するためのカスタムFrameLayout
サブクラスを作成しました。次のように、レイアウトにビューのインスタンスを追加するだけです。
_<com.example.MyHorizontalProgress
Android:layout_width="match_parent"
Android:layout_height="6dp"
app:animationDuration="2000"
app:gradientStartColor="#000"
app:gradientEndColor="#fff"/>
_
グラデーションカラーとアニメーションの速度は、XMLから直接カスタマイズできます。
最初に、カスタム属性を_res/values/attrs.xml
_で定義する必要があります。
_<declare-styleable name="MyHorizontalProgress">
<attr name="animationDuration" format="integer"/>
<attr name="gradientStartColor" format="color"/>
<attr name="gradientEndColor" format="color"/>
</declare-styleable>
_
そして、2つのアニメーションビューを膨らませるレイアウトリソースファイルがあります。
_<merge xmlns:Android="http://schemas.Android.com/apk/res/Android">
<View
Android:id="@+id/one"
Android:layout_width="match_parent"
Android:layout_height="match_parent"/>
<View
Android:id="@+id/two"
Android:layout_width="match_parent"
Android:layout_height="match_parent"/>
</merge>
_
そしてここにJavaがあります:
_public class MyHorizontalProgress extends FrameLayout {
private static final int DEFAULT_ANIMATION_DURATION = 2000;
private static final int DEFAULT_START_COLOR = Color.RED;
private static final int DEFAULT_END_COLOR = Color.BLUE;
private final View one;
private final View two;
private int animationDuration;
private int startColor;
private int endColor;
private int laidOutWidth;
public MyHorizontalProgress(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.my_horizontal_progress, this);
readAttributes(attrs);
one = findViewById(R.id.one);
two = findViewById(R.id.two);
ViewCompat.setBackground(one, new GradientDrawable(LEFT_RIGHT, new int[]{ startColor, endColor }));
ViewCompat.setBackground(two, new GradientDrawable(LEFT_RIGHT, new int[]{ endColor, startColor }));
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
laidOutWidth = MyHorizontalProgress.this.getWidth();
ValueAnimator animator = ValueAnimator.ofInt(0, 2 * laidOutWidth);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setDuration(animationDuration);
animator.addUpdateListener(updateListener);
animator.start();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
else {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
}
private void readAttributes(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MyHorizontalProgress);
animationDuration = a.getInt(R.styleable.MyHorizontalProgress_animationDuration, DEFAULT_ANIMATION_DURATION);
startColor = a.getColor(R.styleable.MyHorizontalProgress_gradientStartColor, DEFAULT_START_COLOR);
endColor = a.getColor(R.styleable.MyHorizontalProgress_gradientEndColor, DEFAULT_END_COLOR);
a.recycle();
}
private ValueAnimator.AnimatorUpdateListener updateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int offset = (int) valueAnimator.getAnimatedValue();
one.setTranslationX(calculateOneTranslationX(laidOutWidth, offset));
two.setTranslationX(calculateTwoTranslationX(laidOutWidth, offset));
}
};
private int calculateOneTranslationX(int width, int offset) {
return (-1 * width) + offset;
}
private int calculateTwoTranslationX(int width, int offset) {
if (offset <= width) {
return offset;
}
else {
return (-2 * width) + offset;
}
}
}
_
Javaの動作は非常に簡単です。ここでは、何が行われているのかを段階的に説明します。
FrameLayout
に追加しますAttributeSet
からアニメーションの継続時間と色の値を読み取りますone
とtwo
の子ビューを見つけます(あまりクリエイティブな名前ではありません、わかっています)GradientDrawable
を作成し、背景として適用しますOnGlobalLayoutListener
を使用してアニメーションを設定しますOnGlobalLayoutListener
を使用すると、プログレスバーの幅の実際の値が確実に取得され、レイアウトされるまでアニメーションが開始されなくなります。
アニメーションもかなりシンプルです。 _0
_と_2 * width
_の間の値を出力する、無限に繰り返すValueAnimator
を設定します。 「更新」イベントごとに、生成された「更新」値から計算された値を使用して、子ビューでupdateListener
がsetTranslationX()
を呼び出します。
以上です!上記のいずれかが不明確である場合はお知らせください。喜んでお手伝いいたします。
final View bar = view.findViewById(R.id.progress);
final GradientDrawable background = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[]{Color.BLUE, Color.RED, Color.BLUE, Color.RED});
bar.setBackground(background);
bar.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(final View v, final int left, final int top, final int right, final int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
background.setBounds(-2 * v.getWidth(), 0, v.getWidth(), v.getHeight());
ValueAnimator animation = ValueAnimator.ofInt(0, 2 * v.getWidth());
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
background.setBounds(-2 * v.getWidth() + (int) animation.getAnimatedValue(), 0, v.getWidth() + (int) animation.getAnimatedValue(), v.getHeight());
}
});
animation.setRepeatMode(ValueAnimator.RESTART);
animation.setInterpolator(new LinearInterpolator());
animation.setRepeatCount(ValueAnimator.INFINITE);
animation.setDuration(3000);
animation.start();
}
});
これはテスト用のビューです:
<FrameLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:layout_gravity="center" >
<View
Android:id="@+id/progress"
Android:layout_width="match_parent"
Android:layout_height="40dp"/>
</FrameLayout>
一部の人を助けるかもしれない 'Android開発者のコードを少し変更しました。
アニメーションが適切にサイズ変更されていないようだったので、これを修正し、アニメーションの速度を少し設定しやすく(ピクセルベースではなく秒単位)、initコードを再配置して、アクティビティのコードなしでレイアウトxmlに直接埋め込めるようにしました。
ScrollingProgressBar.kt
package com.test
import Android.content.Context
import Android.util.AttributeSet
import Android.widget.ProgressBar
import Android.animation.TimeAnimator
import Android.graphics.*
import Android.graphics.drawable.Animatable
import Android.graphics.drawable.Drawable
class ScrollingGradient : Drawable(), Animatable, TimeAnimator.TimeListener {
private val Paint = Paint()
private var x: Float = 0.toFloat()
private val animator = TimeAnimator()
private var pixelsPerSecond: Float = 0f
private val animationTime: Int = 2
init {
animator.setTimeListener(this)
}
override fun onBoundsChange(bounds: Rect) {
Paint.shader = LinearGradient(0f, 0f, bounds.width().toFloat(), 0f, Color.parseColor("#00D3D3D3"), Color.parseColor("#CCD3D3D3"), Shader.TileMode.MIRROR)
pixelsPerSecond = ((bounds.right - bounds.left) / animationTime).toFloat()
}
override fun draw(canvas: Canvas) {
canvas.clipRect(bounds)
canvas.translate(x, 0f)
canvas.drawPaint(Paint)
}
override fun setAlpha(alpha: Int) {}
override fun setColorFilter(colorFilter: ColorFilter?) {}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
override fun start() {
animator.start()
}
override fun stop() {
animator.cancel()
}
override fun isRunning(): Boolean = animator.isRunning
override fun onTimeUpdate(animation: TimeAnimator, totalTime: Long, deltaTime: Long) {
x = pixelsPerSecond * totalTime / 1000
invalidateSelf()
}
}
class ScrollingProgressBar : ProgressBar {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
init {
this.indeterminateDrawable = ScrollingGradient()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
this.indeterminateDrawable.setBounds(this.left, this.top, this.right, this.bottom)
}
}
レイアウトxml(com.test.ScrollingProgressBarを上記のコードの場所に置き換えます)
<com.test.ScrollingProgressBar
Android:id="@+id/progressBar1"
Android:background="#464646"
style="?android:attr/progressBarStyleHorizontal"
Android:layout_width="match_parent"
Android:layout_height="80dp"
Android:gravity="center"
Android:indeterminateOnly="true"/>
プログレスバーとして表示するために必要な色を定義するさまざまなドローアブルがある場合、それを実現できます。
AnimationDrawable animation_listを使用します
<animation-list Android:id="@+id/selected" Android:oneshot="false">
<item Android:drawable="@drawable/color1" Android:duration="50" />
<item Android:drawable="@drawable/color2" Android:duration="50" />
<item Android:drawable="@drawable/color3" Android:duration="50" />
<item Android:drawable="@drawable/color4" Android:duration="50" />
-----
-----
</animation-list>
そして、Activity/xmlで、これをプログレスバーのバックグラウンドリソースとして設定します。
その後、次のようにします
// Get the background, which has been compiled to an AnimationDrawable object.
AnimationDrawable frameAnimation = (AnimationDrawable)prgressBar.getBackground();
// Start the animation (looped playback by default).
frameAnimation.start();
青から赤および赤から青のグラデーション効果をそれぞれカバーするようにそれぞれのドローアブルを取得する場合、アニメーションリストでcolor1、color2などとして言及する必要がある画像
このアプローチは、複数の静止画像でGIF画像を作成する方法に似ています。