我正在操作栏上执行“KenBurns effect”(演示here),如this library的示例所示(移动的图标除外)我自己就这样做了。)
事实上,我甚至在很久以前就问过它(here),此时我甚至都不知道它的名字。我确信我找到了解决方案,但它有一些问题。
此外,由于我有时会显示设备中的图像,因此有些图像甚至需要旋转,所以我使用了rotateDrawable(如图here所示)。
当前实现无法处理动态提供的多个位图(例如,来自Internet),甚至不会查看输入图像的大小。
相反,它只是以随机的方式进行缩放和平移,因此很多时候它可以缩放太多/很少,并且可以显示空白空间。
以下是与问题相关的代码:
private float pickScale() {
return MIN_SCALE_FACTOR + this.random.nextFloat() * (MAX_SCALE_FACTOR - MIN_SCALE_FACTOR);
}
private float pickTranslation(final int value, final float ratio) {
return value * (ratio - 1.0f) * (this.random.nextFloat() - 0.5f);
}
public void animate(final ImageView view) {
final float fromScale = pickScale();
final float toScale = pickScale();
final float fromTranslationX = pickTranslation(view.getWidth(), fromScale);
final float fromTranslationY = pickTranslation(view.getHeight(), fromScale);
final float toTranslationX = pickTranslation(view.getWidth(), toScale);
final float toTranslationY = pickTranslation(view.getHeight(), toScale);
start(view, KenBurnsView.DELAY_BETWEEN_IMAGE_SWAPPING_IN_MS, fromScale, toScale, fromTranslationX,
fromTranslationY, toTranslationX, toTranslationY);
}
这是动画本身的一部分,它动画当前的ImageView:
private void start(View view, long duration, float fromScale, float toScale, float fromTranslationX, float fromTranslationY, float toTranslationX, float toTranslationY) {
view.setScaleX(fromScale);
view.setScaleY(fromScale);
view.setTranslationX(fromTranslationX);
view.setTranslationY(fromTranslationY);
ViewPropertyAnimator propertyAnimator = view.animate().translationX(toTranslationX).translationY(toTranslationY).scaleX(toScale).scaleY(toScale).setDuration(duration);
propertyAnimator.start();
}
如您所见,这不会查看视图/位图大小,只是随机选择如何缩放和平移。
我已经使用动态位图,但我不明白要改变什么,以便它能正确处理大小。
我还注意到有另一个库(here)可以完成这项工作,但它也有同样的问题,而且更难理解如何修复它们。加上它随机崩溃。 Here's我发布了一篇关于它的帖子。
为了正确实现Ken-Burns效果应该怎么做才能处理动态创建的位图?
我想也许最好的解决方案是自定义ImageView绘制内容的方式,这样在任何给定的时间,它都会显示给它的位图的一部分,真正的动画将是在位图的两个矩形之间。可悲的是,我不知道该怎么做。
同样,问题不在于获取位图或解码。它是关于如何使它们在没有崩溃或奇怪的放大/缩小显示空白空间的情况下使用此效果。
答案 0 :(得分:11)
我查看了KenBurnsView
的源代码,实际上很难实现您想要的功能,但我必须首先澄清一些事项:
当前的实现无法处理多个位图 动态给出(例如,来自互联网),......
如果您知道自己在做什么,从互联网上动态下载图片并不困难,但有很多方法可以做到。许多人实际上并没有提出他们自己的解决方案,而是使用像Volley这样的网络库来下载图像,或者他们直接进入Picasso或类似的东西。就个人而言,我大多使用自己的帮助程序类,但你必须决定你想要下载图像的确切程度。使用像Picasso这样的库很可能是最适合您的解决方案。我在此答案中的代码示例将使用Picasso库,以下是如何使用Picasso的快速示例:
Picasso.with(context).load("http://foo.com/bar.png").into(imageView);
...并且甚至不看输入图像'大小
我真的不明白你的意思。在KenBurnsView
内部使用ImageViews
来显示图像。它们负责正确缩放和显示图像,并且它们肯定会考虑图像的大小。我认为您的混淆可能是由为scaleType
设置的ImageViews
引起的。如果您查看包含R.layout.view_kenburns
布局的布局文件KenBurnsView
,您会看到:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image0"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/image1"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
请注意,有两个ImageViews
而不是一个来创建交叉渐变效果。重要的部分是此标记,可在ImageViews
:
android:scaleType="centerCrop"
这样做是告诉ImageView
:
ImageView
ImageView
ImageView
,则会将其裁剪为ImageView
因此,在当前状态下,KenBurnsView
内的图像可能始终被裁剪。如果您希望图像缩放以完全适合ImageView
,那么无需裁剪或删除任何内容,您需要将scaleType
更改为其中之一:
android:scaleType="fitCenter"
android:scaleType="centerInside"
我不记得这两者之间的确切差异,但它们都应该具有缩放图像所需的效果,使其适合ImageView
内的X和Y轴同时适用时间以ImageView
。
重要提示:更改scaleType
可能会混淆KenBurnsView
!
如果您真的只是使用KenBurnsView
来显示两张图片,那么更改scaleType
除了图像的显示方式外,还有其他问题,但如果您调整KenBurnsView
的大小 - 例如在动画中 - ImageViews
将scaleType
设置为centerCrop
以外的其他内容,您将失去视差效果!使用centerCrop
作为scaleType
的{{1}}是一种创建视差效果的快捷方法。这个技巧的缺点可能是您注意到的: ImageView
中的图像很可能会被裁剪而不完全可见!
如果您查看布局,可以看到其中的所有ImageView
都有Views
match_parent
和layout_height
。对于某些图像,这也可能是一个问题,因为layout_width
约束和某些match_parent
有时会在图像比scaleTypes
小得多或大于ImageView
时产生奇怪的结果。
翻译动画还会考虑图像的大小 - 或至少考虑ImageView
的大小。如果您查看animate(...)
和pickTranslation(...)
的源代码,您会看到:
// The value which is passed to pickTranslation() is the size of the View!
private float pickTranslation(final int value, final float ratio) {
return value * (ratio - 1.0f) * (this.random.nextFloat() - 0.5f);
}
public void animate(final ImageView view) {
final float fromScale = pickScale();
final float toScale = pickScale();
// Depending on the axis either the width or the height is passed to pickTranslation()
final float fromTranslationX = pickTranslation(view.getWidth(), fromScale);
final float fromTranslationY = pickTranslation(view.getHeight(), fromScale);
final float toTranslationX = pickTranslation(view.getWidth(), toScale);
final float toTranslationY = pickTranslation(view.getHeight(), toScale);
start(view, KenBurnsView.DELAY_BETWEEN_IMAGE_SWAPPING_IN_MS, fromScale, toScale, fromTranslationX, fromTranslationY, toTranslationX, toTranslationY);
}
因此视图已经考虑了图像大小以及计算翻译时图像缩放的程度。所以这个工作原理的概念是可以的,我看到的唯一问题是开始值和结束值都是随机的,而这两个值之间没有任何依赖关系。这意味着一件简单的事情:动画的起点和终点可能是完全相同的位置,也可能彼此非常接近。因此,动画有时可能非常重要,而其他时间根本不会引起注意。
我可以想出三种主要方法来解决这个问题:
fromScale
应该是1.2f
和1.4f
之间的随机值,toScale
应该是1.6f
和1.8f
之间的随机值。< / LI>
无论您选择方法#1还是#2,您都需要这种方法:
// Returns a random value between min and max
private float randomRange(float min, float max) {
return random.nextFloat() * (max - min) + max;
}
这里我修改了animate()
方法以强制动画的起点和终点之间有一定的距离:
public void animate(View view) {
final float fromScale = randomRange(1.2f, 1.4f);
final float fromTranslationX = pickTranslation(view.getWidth(), fromScale);
final float fromTranslationY = pickTranslation(view.getHeight(), fromScale);
final float toScale = randomRange(1.6f, 1.8f);
final float toTranslationX = pickTranslation(view.getWidth(), toScale);
final float toTranslationY = pickTranslation(view.getHeight(), toScale);
start(view, this.mSwapMs, fromScale, toScale, fromTranslationX, fromTranslationY, toTranslationX, toTranslationY);
}
如您所见,我只需要修改计算fromScale
和toScale
的方式,因为翻译值是根据比例值计算的。这不是100%修复,但它是一个很大的改进。
KenBurnsView
要修复KenBurnsView
,您可以实施我上面提到的建议。此外,我们需要实现一种动态添加图像的方法。 KenBurnsView
如何处理图像的实现有点奇怪。我们需要稍微修改一下。由于我们使用Picasso,这实际上非常简单:
基本上你只需要修改swapImage()
方法,我就这样测试它并且它正在工作:
private void swapImage() {
if (this.urlList.size() > 0) {
if(mActiveImageIndex == -1) {
mActiveImageIndex = 1;
animate(mImageViews[mActiveImageIndex]);
return;
}
final int inactiveIndex = mActiveImageIndex;
mActiveImageIndex = (1 + mActiveImageIndex) % mImageViews.length;
Log.d(TAG, "new active=" + mActiveImageIndex);
String url = this.urlList.get(this.urlIndex++);
this.urlIndex = this.urlIndex % this.urlList.size();
final ImageView activeImageView = mImageViews[mActiveImageIndex];
activeImageView.setAlpha(0.0f);
Picasso.with(this.context).load(url).into(activeImageView, new Callback() {
@Override
public void onSuccess() {
ImageView inactiveImageView = mImageViews[inactiveIndex];
animate(activeImageView);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(mFadeInOutMs);
animatorSet.playTogether(
ObjectAnimator.ofFloat(inactiveImageView, "alpha", 1.0f, 0.0f),
ObjectAnimator.ofFloat(activeImageView, "alpha", 0.0f, 1.0f)
);
animatorSet.start();
}
@Override
public void onError() {
Log.i(LOG_TAG, "Could not download next image");
}
});
}
}
我省略了一些简单的部分,urlList
只是一个List<String>
,其中包含我们想要显示的图像的所有网址,urlIndex
用于循环遍历urlList
1}}。我将动画移动到Callback
。这样,图像将在后台下载,一旦成功下载图像,动画就会播放,ImageViews
将交叉淡入淡出。现在可以删除KenBurnsView
中的许多旧代码,例如现在不需要方法setResourceIds()
或fillImageViews()
。
KenBurnsView
+毕加索 您链接到的第二个图书馆this one实际上包含了更多KenBurnsView
。我在上面讨论的KenBurnsView
是FrameLayout
的子类,这个View
采用的方法存在一些问题。来自second library的KenBurnsView
是ImageView
的子类,这已经是一个巨大的进步。因此,我们可以直接在KenBurnsView
上使用Picasso等图像加载器库,而我们自己也不必处理任何事情。你说你遇到second library的随机崩溃?我在过去的几个小时里对它进行了相当广泛的测试,并没有遇到过一次崩溃。
使用second library和Picasso中的KenBurnsView
,这一切都变得非常简单且代码很少,您只需要在xml中创建一个KenBurnsView
:
<com.flaviofaria.kenburnsview.KenBurnsView
android:id="@+id/kbvExample"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image" />
然后在你的Fragment
中首先必须在布局中找到视图,然后在onViewCreated()
中我们用Picasso加载图像:
private KenBurnsView kbvExample;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_kenburns_test, container, false);
this.kbvExample = (KenBurnsView) view.findViewById(R.id.kbvExample);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Picasso.with(getActivity()).load(IMAGE_URL).into(this.kbvExample);
}
我测试了运行Android 4.4.2的Nexus 5上的所有内容。由于使用了ViewPropertyAnimators
,因此这应该在API级别16以下甚至12级都可以兼容。
我在这里省略了几行代码,所以如果你有任何问题可以随意提问!