Android 和多线程中的动画
Animation in Android and Multithreading
在尝试向 ImageView 添加动画时,我似乎没有正确使用多线程,这导致我得到不正确的结果。
这是我调用动画的部分>>
startAnimation(gameController.getSecondImage()); // 1
...
startAnimation(gameController.getFirstImage()); // 2
...
startAnimation(gameController.getSecondImage()); // 3
...
这里的问题是,当我没有为 startAnimation()
函数创建单独的线程时,所有 3 个调用同时发生,一切都变得一团糟。但是现在它仍然无法正常工作,并且第 1 次和第 3 次调用混乱不堪。
public void startAnimation(final ImageView image1) {
final ImageView image2 = cardViewMap.get(cardSwapMap.get(image1.getId()));
Runnable animate = new Runnable() {
@Override
public void run() {
animationController.animate(image1, image2);
}
};
Thread animationThread = new Thread(animate);
animationThread.start();
try { //
animationThread.join(); //
} catch (InterruptedException e) { // Added Later
Log.d("ERROR", e.toString()); //
} //
}
我尝试用 join()
进行试验,但没有成功,它现在给出了更激进的结果。我认为使用 join()
将确保在开始新的 startAnimation()
之前执行之前对 startAnimation()
的调用。
编辑: 我已经跟踪了这个问题,问题是我的 startAnimation()
函数在主 Looper 为 运行 时被调用。所以它是在对函数的所有三个调用都完成后执行的。我需要确保 startAnimation()
在我进行调用时运行,并在下一次调用同一函数之前执行。有什么想法可以确保这一点吗?
尝试 运行 这段代码将帮助您理解解决方法,一旦您 运行 它会让您更容易理解,
为其命名布局animation_test_activity.xml代码如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_launcher"
android:visibility="invisible" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/imageView3"
android:layout_alignParentTop="true"
android:src="@drawable/ic_launcher"
android:visibility="invisible" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/imageView3"
android:layout_alignParentTop="true"
android:src="@drawable/ic_launcher"
android:visibility="invisible" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/imageView3"
android:layout_alignParentBottom="true"
android:text="click me" />
</RelativeLayout>
现在创建一个名为 AnimationTest.java 的 activity,代码如下所述。
package com.some.servewrtest;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AnimationSet;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ImageView;
public class AnimationTest extends Activity implements OnClickListener {
private ImageView[] imageViews = new ImageView[3];
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.animation_test_activity);
initializeUI();
}
private void initializeUI() {
// TODO Auto-generated method stub
imageViews[0] = (ImageView) findViewById(R.id.imageView1);
imageViews[1] = (ImageView) findViewById(R.id.imageView2);
imageViews[2] = (ImageView) findViewById(R.id.imageView3);
button = (Button)findViewById(R.id.button1);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
AnimationSet set = new AnimationSet(true);
TranslateAnimation[] animations = new TranslateAnimation[3];
int duration = 300;
animations[0] = createCoins(0, duration*1, 0, 0, 0, 270);
animations[1] = createCoins(1, duration*2, 0, -100, 0, 270);
animations[2] = createCoins(2, duration*3, 0, 100, 0, 270);
set.addAnimation(animations[0]);
set.addAnimation(animations[1]);
set.addAnimation(animations[2]);
set.start();
}
private TranslateAnimation createCoins(int i, int duration, int fromXDelta, int toXDelta, int fromYDelta, int toYDelta) {
// TODO Auto-generated method stub
TranslateAnimation animation = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
animation.setDuration(duration+1900);
animation.setFillAfter(false);
imageViews[i].setVisibility(ImageView.VISIBLE);
imageViews[i].setAnimation(animation);
imageViews[i].setVisibility(ImageView.INVISIBLE);
return animation;
}
}
此代码有效,您也可以对其进行测试,您可以自定义使用它的方式。
现在我们有了一个工作示例,我们将看看我们是如何做到这一点的,
首先,如果您尝试使用 AnimationSet 或为同一图像制作不同的 TranslateAnimation 引用类型来一遍又一遍地为同一图像制作动画,这将不起作用,因为单个图像试图几乎同时出现在两个不同的位置。所以发生的事情是只看到第一个动画而其他动画被 android 忽略,这是重复方法在 android 中不起作用的结果之一。
为了解决这个问题,我们可以做的是拥有尽可能多的视图,这次我们需要在每个单独的视图上显示不同的动画。它的工作原理是每个动画都与单个视图相关,并且它们可以同时工作。
我无法告诉你不要选择多线程,但请记住你正在尝试在 UI 线程上执行此操作,所以如果你的应用程序中有很多动画,它会使你的应用程序陷入困境这些线程很有可能。
如果你想在特定动画结束时收听事件让我们说动画然后这样做
animation..setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
Log.d(TAG, "Animation has started");
}
@Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
Log.d(TAG, "Animation is repeat");
}
@Override
public void onAnimationEnd(Animation animation) {
// TODO Auto-generated method stub
Log.d(TAG, "Animation is over");
}
});
通过这样做,您可以在上一个动画结束时开始一个新动画:)
我尝试使用 Android 工具但失败了。我回去用多线程修复它。
虽然我更改了很多其他代码,但这是重要的代码之一 w.r.t。我的问题-
package thakur.rahul.colourmemory.controller;
import thakur.rahul.colourmemory.view.Animation.DisplayNextView;
import thakur.rahul.colourmemory.view.Animation.Flip3dAnimation;
import android.app.Activity;
import android.view.animation.AccelerateInterpolator;
import android.widget.ImageView;
public class AnimationController implements Runnable {
private Activity activity;
private ImageView image1;
private ImageView image2;
private boolean isFirstImage = true;
private volatile static int numThread = 1;
private volatile static int threadAllowedToRun = 1;
private int myThreadID;
private volatile static Object myLock = new Object();
private volatile static boolean hasNotSlept = true;
public volatile static boolean fixForMatch = false;
public AnimationController(Activity activity, ImageView image1, ImageView image2) {
this.activity = activity;
this.image1 = image1;
this.image2 = image2;
this.myThreadID = numThread++;
}
@Override
public void run() {
synchronized (myLock) {
while (myThreadID != threadAllowedToRun)
try {
myLock.wait();
} catch (InterruptedException e) {
} catch (Exception e) {
}
animate();
if (myThreadID % 2 == 0)
if (hasNotSlept || fixForMatch)
try {
Thread.sleep(1000);
hasNotSlept = !hasNotSlept;
} catch (InterruptedException e) {
}
else
hasNotSlept = !hasNotSlept;
myLock.notifyAll();
threadAllowedToRun++;
}
}
public void animate() {
if (isFirstImage) {
applyRotation(0, 90);
isFirstImage = !isFirstImage;
} else {
applyRotation(0, -90);
isFirstImage = !isFirstImage;
}
}
private void applyRotation(float start, float end) {
final float centerX = image1.getWidth() / 2.0f;
final float centerY = image1.getHeight() / 2.0f;
final Flip3dAnimation rotation = new Flip3dAnimation(start, end, centerX, centerY);
rotation.setDuration(300);
rotation.setFillAfter(true);
rotation.setInterpolator(new AccelerateInterpolator());
rotation.setAnimationListener(new DisplayNextView(isFirstImage, image1, image2));
if (isFirstImage)
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
image1.startAnimation(rotation);
}
});
else
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
image2.startAnimation(rotation);
}
});
}
}
这是从主控制器调用的-
public void startAnimation(ImageView image1) {
ImageView image2 = cardViewMap.get(cardSwapMap.get(image1.getId()));
animationController = new AnimationController(activity, image1, image2);
animationThread = new Thread(animationController);
animationThread.start();
}
基本上,每次我需要 运行 动画时,我都会制作并标记线程,这样我就可以按顺序 运行 它。
如果您有更好的解决方案,请添加您的答案。
在尝试向 ImageView 添加动画时,我似乎没有正确使用多线程,这导致我得到不正确的结果。
这是我调用动画的部分>>
startAnimation(gameController.getSecondImage()); // 1
...
startAnimation(gameController.getFirstImage()); // 2
...
startAnimation(gameController.getSecondImage()); // 3
...
这里的问题是,当我没有为 startAnimation()
函数创建单独的线程时,所有 3 个调用同时发生,一切都变得一团糟。但是现在它仍然无法正常工作,并且第 1 次和第 3 次调用混乱不堪。
public void startAnimation(final ImageView image1) {
final ImageView image2 = cardViewMap.get(cardSwapMap.get(image1.getId()));
Runnable animate = new Runnable() {
@Override
public void run() {
animationController.animate(image1, image2);
}
};
Thread animationThread = new Thread(animate);
animationThread.start();
try { //
animationThread.join(); //
} catch (InterruptedException e) { // Added Later
Log.d("ERROR", e.toString()); //
} //
}
我尝试用 join()
进行试验,但没有成功,它现在给出了更激进的结果。我认为使用 join()
将确保在开始新的 startAnimation()
之前执行之前对 startAnimation()
的调用。
编辑: 我已经跟踪了这个问题,问题是我的 startAnimation()
函数在主 Looper 为 运行 时被调用。所以它是在对函数的所有三个调用都完成后执行的。我需要确保 startAnimation()
在我进行调用时运行,并在下一次调用同一函数之前执行。有什么想法可以确保这一点吗?
尝试 运行 这段代码将帮助您理解解决方法,一旦您 运行 它会让您更容易理解,
为其命名布局animation_test_activity.xml代码如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_launcher"
android:visibility="invisible" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/imageView3"
android:layout_alignParentTop="true"
android:src="@drawable/ic_launcher"
android:visibility="invisible" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/imageView3"
android:layout_alignParentTop="true"
android:src="@drawable/ic_launcher"
android:visibility="invisible" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/imageView3"
android:layout_alignParentBottom="true"
android:text="click me" />
</RelativeLayout>
现在创建一个名为 AnimationTest.java 的 activity,代码如下所述。
package com.some.servewrtest;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AnimationSet;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ImageView;
public class AnimationTest extends Activity implements OnClickListener {
private ImageView[] imageViews = new ImageView[3];
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.animation_test_activity);
initializeUI();
}
private void initializeUI() {
// TODO Auto-generated method stub
imageViews[0] = (ImageView) findViewById(R.id.imageView1);
imageViews[1] = (ImageView) findViewById(R.id.imageView2);
imageViews[2] = (ImageView) findViewById(R.id.imageView3);
button = (Button)findViewById(R.id.button1);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
AnimationSet set = new AnimationSet(true);
TranslateAnimation[] animations = new TranslateAnimation[3];
int duration = 300;
animations[0] = createCoins(0, duration*1, 0, 0, 0, 270);
animations[1] = createCoins(1, duration*2, 0, -100, 0, 270);
animations[2] = createCoins(2, duration*3, 0, 100, 0, 270);
set.addAnimation(animations[0]);
set.addAnimation(animations[1]);
set.addAnimation(animations[2]);
set.start();
}
private TranslateAnimation createCoins(int i, int duration, int fromXDelta, int toXDelta, int fromYDelta, int toYDelta) {
// TODO Auto-generated method stub
TranslateAnimation animation = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
animation.setDuration(duration+1900);
animation.setFillAfter(false);
imageViews[i].setVisibility(ImageView.VISIBLE);
imageViews[i].setAnimation(animation);
imageViews[i].setVisibility(ImageView.INVISIBLE);
return animation;
}
}
此代码有效,您也可以对其进行测试,您可以自定义使用它的方式。
现在我们有了一个工作示例,我们将看看我们是如何做到这一点的,
首先,如果您尝试使用 AnimationSet 或为同一图像制作不同的 TranslateAnimation 引用类型来一遍又一遍地为同一图像制作动画,这将不起作用,因为单个图像试图几乎同时出现在两个不同的位置。所以发生的事情是只看到第一个动画而其他动画被 android 忽略,这是重复方法在 android 中不起作用的结果之一。
为了解决这个问题,我们可以做的是拥有尽可能多的视图,这次我们需要在每个单独的视图上显示不同的动画。它的工作原理是每个动画都与单个视图相关,并且它们可以同时工作。
我无法告诉你不要选择多线程,但请记住你正在尝试在 UI 线程上执行此操作,所以如果你的应用程序中有很多动画,它会使你的应用程序陷入困境这些线程很有可能。
如果你想在特定动画结束时收听事件让我们说动画然后这样做
animation..setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
Log.d(TAG, "Animation has started");
}
@Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
Log.d(TAG, "Animation is repeat");
}
@Override
public void onAnimationEnd(Animation animation) {
// TODO Auto-generated method stub
Log.d(TAG, "Animation is over");
}
});
通过这样做,您可以在上一个动画结束时开始一个新动画:)
我尝试使用 Android 工具但失败了。我回去用多线程修复它。
虽然我更改了很多其他代码,但这是重要的代码之一 w.r.t。我的问题-
package thakur.rahul.colourmemory.controller;
import thakur.rahul.colourmemory.view.Animation.DisplayNextView;
import thakur.rahul.colourmemory.view.Animation.Flip3dAnimation;
import android.app.Activity;
import android.view.animation.AccelerateInterpolator;
import android.widget.ImageView;
public class AnimationController implements Runnable {
private Activity activity;
private ImageView image1;
private ImageView image2;
private boolean isFirstImage = true;
private volatile static int numThread = 1;
private volatile static int threadAllowedToRun = 1;
private int myThreadID;
private volatile static Object myLock = new Object();
private volatile static boolean hasNotSlept = true;
public volatile static boolean fixForMatch = false;
public AnimationController(Activity activity, ImageView image1, ImageView image2) {
this.activity = activity;
this.image1 = image1;
this.image2 = image2;
this.myThreadID = numThread++;
}
@Override
public void run() {
synchronized (myLock) {
while (myThreadID != threadAllowedToRun)
try {
myLock.wait();
} catch (InterruptedException e) {
} catch (Exception e) {
}
animate();
if (myThreadID % 2 == 0)
if (hasNotSlept || fixForMatch)
try {
Thread.sleep(1000);
hasNotSlept = !hasNotSlept;
} catch (InterruptedException e) {
}
else
hasNotSlept = !hasNotSlept;
myLock.notifyAll();
threadAllowedToRun++;
}
}
public void animate() {
if (isFirstImage) {
applyRotation(0, 90);
isFirstImage = !isFirstImage;
} else {
applyRotation(0, -90);
isFirstImage = !isFirstImage;
}
}
private void applyRotation(float start, float end) {
final float centerX = image1.getWidth() / 2.0f;
final float centerY = image1.getHeight() / 2.0f;
final Flip3dAnimation rotation = new Flip3dAnimation(start, end, centerX, centerY);
rotation.setDuration(300);
rotation.setFillAfter(true);
rotation.setInterpolator(new AccelerateInterpolator());
rotation.setAnimationListener(new DisplayNextView(isFirstImage, image1, image2));
if (isFirstImage)
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
image1.startAnimation(rotation);
}
});
else
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
image2.startAnimation(rotation);
}
});
}
}
这是从主控制器调用的-
public void startAnimation(ImageView image1) {
ImageView image2 = cardViewMap.get(cardSwapMap.get(image1.getId()));
animationController = new AnimationController(activity, image1, image2);
animationThread = new Thread(animationController);
animationThread.start();
}
基本上,每次我需要 运行 动画时,我都会制作并标记线程,这样我就可以按顺序 运行 它。
如果您有更好的解决方案,请添加您的答案。