我查看了源代码,KenBurnsView
实际上实现您想要的功能并不难,但我必须首先澄清一些事情:
1.动态加载图片
当前的实现无法处理动态给出的多个位图(例如来自 Internet),...
如果您知道自己在做什么,从 Internet 动态下载图像并不难,但是有很多方法可以做到。许多人实际上并没有提出自己的解决方案,而是使用像Volley这样的网络库来下载图像,或者他们直接使用Picasso或类似的东西。就我个人而言,我主要使用我自己的一组帮助类,但你必须决定你想如何下载图像。使用像Picasso这样的库很可能是您的最佳解决方案。我在这个答案中的代码示例将使用Picasso库,这是一个如何使用Picasso的快速示例:
Picasso.with(context).load("http://foo.com/bar.png").into(imageView);
2. 图像尺寸
...甚至不看输入图像的大小。
我真的不明白你的意思。在内部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"
我不记得这两者之间的确切区别,但它们都应该具有缩放图像的预期效果,因此它适合在 X 和 Y 轴上,ImageView
同时在ImageView
.
重要提示:改变scaleType
潜在的混乱KenBurnsView
!
如果您真的只是使用KenBurnsView
来显示两个图像,那么除了图像的显示方式之外,更改scaleType
将无关紧要,但是如果您调整大小KenBurnsView
- 例如在动画中 - 并且将ImageViews
设置scaleType
设置为其他内容,centerCrop
那么您将失去视差效果!使用centerCrop
as scaleType
of anImageView
是一种创建类似视差效果的快速简便的方法。这个技巧的缺点可能是你注意到的:图中的图像ImageView
很可能会被裁剪并且不完全可见!
如果您查看布局,您会发现其中的所有内容Views
都有match_parent
aslayout_height
和layout_width
. 这对于某些图像作为match_parent
约束也可能是一个问题,并且scaleTypes
当图像远小于或大于ImageView
.
translate 动画还考虑了图像的大小 - 或者至少是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
- 实现固定的平移和缩放动画(换句话说就是无聊的方式)。
无论您选择方法#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% 的修复,但它是一个很大的改进。
3. 解决方案#1:修复KenBurnsView
(如果可能,使用解决方案#2)
要解决这个问题,KenBurnsView
您可以实施我上面提到的建议。此外,我们需要实现一种动态添加图像的方法。如何KenBurnsView
处理图像的实现有点奇怪。我们需要稍微修改一下。由于我们使用的是毕加索,这实际上非常简单:
本质上你只需要修改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>
包含我们要显示的图像的所有 url,urlIndex
用于循环浏览urlList
. 我将动画移到Callback
. 这样,图像将在后台下载,一旦图像下载成功,动画就会播放并且ImageViews
会淡入淡出。KenBurnsView
现在可以删除许多旧代码,例如方法setResourceIds()
或fillImageViews()
现在是不必要的。
4. 解决方案 #2:更好KenBurnsView
+ 毕加索
您链接到的第二个库,这个,实际上包含一个更好的KenBurnsView
. KenBurnsView
我上面谈论的是一个子类,这种FrameLayout
方法存在一些问题View
。来自KenBurnsView
第二个库的 是 的子类ImageView
,这已经是一个巨大的改进。正因为如此,我们可以直接在上面使用像PicassoKenBurnsView
这样的图像加载器库,而我们不必自己处理任何事情。您说您在使用第二个库时遇到随机崩溃?在过去的几个小时里,我一直在对其进行相当广泛的测试,并且没有遇到任何崩溃。
使用KenBurnsView
第二个库和毕加索,这一切都变得非常简单,只需几行代码,您只需KenBurnsView
在 xml 中创建一个示例:
<com.flaviofaria.kenburnsview.KenBurnsView
android:id="@+id/kbvExample"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image" />
然后在你的Fragment
你首先必须在布局中找到视图,然后onViewCreated()
我们用毕加索加载图像:
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);
}
5. 测试
我在运行 Android 4.4.2 的 Nexus 5 上测试了所有内容。由于ViewPropertyAnimators
使用了它,它应该在某个地方兼容到 API 级别 16,甚至可能是 12。
我在这里和那里省略了几行代码,所以如果您有任何问题,请随时提问!