写在最前面:这里所指的自定义GIF控件指的是一个继承自imageview的、使用帧动画将一系列图片播放出来的控件,并不是读取GIF图片播放的控件
一、实现内容
-
创建一个自定义控件,可以播放图片资源达成类似GIF的效果
-
在自定义控件中使用自定义属性
-
使用MVVM框架,在viewmodel中可以设置图片资源
二、学习目标
-
学习如何设置自定义属性
-
学习如何将自定义属性进行数据绑定
三、实现
1. 自定义控件
首先完成基本的内容,自定义GIF控件,主要使用到的就是帧动画
public class GifView extends AppCompatImageView {
private AnimationDrawable animationDrawable;
public GifView(Context context) {
super(context);
init();
}
public GifView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
animationDrawable = new AnimationDrawable();
setImageDrawable(animationDrawable);
int[] gifResourceIds = {R.drawable.frame_1, R.drawable.frame_2, R.drawable.frame_3};
setGifResourceIds(gifResourceIds,100);
startGif();
}
public void setGifResourceIds(int[] resourceIds, int duration) {
for (int resourceId : resourceIds) {
animationDrawable.addFrame(getResources().getDrawable(resourceId), duration);
}
}
public void startGif() {
animationDrawable.start();
}
public void stopGif() {
animationDrawable.stop();
}
}
在xml文件中这样就可以使用这个自定义控件
<com.example.gif.GifView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
这是我最开始写的版本,仅仅完成了播放动画的要求,图片也是写死在里面的,当然我也可以选择在MainActivity使用findViewByID,再调用GifView中的setGifResourceIds去设置图片,但是如果我想用MVVM框架的话,这就是一个不合适的写法,所以我考虑使用自定义属性。
2. 自定义属性
- 在res/values/attrs.xml中定义自定义属性:
创建一个XML文件,通常命名为attrs.xml
,位于res/values/
目录下。在这个文件中,你可以定义自己的属性。例如:
<resources>
<declare-styleable name="GifView">
<attr name="gifResourceIds" format="reference" />
<attr name="gifDuration" format="integer" />
</declare-styleable>
</resources>
这里我定义了两个属性,但是后面实际使用我只用了一个,如果大家有兴趣可以自己完成第二个自定义属性的设置。这里gifResourceIds我使用的format是reference,指向一个引用id
- 在布局文件中使用自定义属性: 在布局文件中,使用自定义属性给相应的控件设置值。
首先增加一个新的XML命名空间声明,它告诉Android系统如何解释和处理XML布局文件中的自定义属性。
xmlns:app="http://schemas.android.com/apk/res-auto"
然后就可以使用这个属性
<com.example.gif.GifView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:gifResourceIds=""
/>
但是因为我们还并没有在自定义view中读取和使用这个属性,所以现在它是没有意义的。不过这里我遇到了一个新的问题,我这里想传入的属性值应该是一堆图片——就是我想让他们像GIF一样播放出来的一堆图片,所以我应该传入一个类似数组的东西。
在这里我的解决方案是,在xml中配置一个数组资源,其中包含我想要播放的图片。我之前有提到自定义属性的format是reference,所以我这里传入一个资源id就可以了。
配置数组资源:
在res/values/
目录下创建一个XML文件(例如,arrays.xml):
<resources>
<array name="gifResourceIds">
<item>@drawable/frame_1</item>
<item>@drawable/frame_2</item>
<item>@drawable/frame_3</item>
</array>
</resources>
这样我们就可通过它的资源id R.array.gifResourceIds
来获取它了
- 在自定义控件中读取和使用自定义属性:
现在回到之前的内容,在自定义控件的Java类中,我们需要读取和使用自定义属性的值,这样这个属性才有意义。在构造函数或初始化方法中,可以使用obtainStyledAttributes
方法获取属性值。
TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.GifView);
resData=ta.getResourceId(R.styleable.GifView_gifResourceIds,0);
这里获取该属性的资源ID,并将其存储在变量 resData
中
- 解析数据
这里我们获取了这个数据,但是它是这个数组的资源id,我们需要的是数组内的图片资源,所以我们需要将其中的数据读取出来
private void initRes() {
// 获取引用的资源 ID 数组
if (resData == Resources.ID_NULL) {
return;
}
TypedArray resourceArray = getResources().obtainTypedArray(resData);
int length = resourceArray.length();
resourceIds.clear();
for (int i = 0; i < length; i++) {
resourceIds.add(resourceArray.getResourceId(i, 0));
}
resourceArray.recycle();
}
经过这里我们终于获得图片资源
3. GifView代码
这里先贴上全部源码
public class GifView extends AppCompatImageView {
private final String TAG= "GifView";
private AnimationDrawable animationDrawable;
private int resData;
private ArrayList<Integer> resourceIds = new ArrayList<>();
private ArrayList<Integer> temp;
public GifView(Context context) {
super(context);
Log.d(TAG, "GifView: no param");
}
public GifView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, "GifView: have param");
TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.GifView);
Log.d(TAG, "GifView: ta"+ta);
Log.d(TAG, "GifView: ta.getIndexCount()"+ta.getIndexCount());
resData=ta.getResourceId(R.styleable.GifView_gifResourceIds,0);
initRes();
init();
}
private void initRes() {
// 获取引用的资源 ID 数组
if (resData == Resources.ID_NULL) {
Log.d(TAG, "initRes: resource id is null");
return;
}
TypedArray resourceArray = getResources().obtainTypedArray(resData);
int length = resourceArray.length();
resourceIds.clear();
for (int i = 0; i < length; i++) {
resourceIds.add(resourceArray.getResourceId(i, 0));
}
resourceArray.recycle();
}
public void setGifResourceIds(int gifResourceIds) {
resData= gifResourceIds;
initRes();
init();
}
private void init() {
if (animationDrawable != null) {
if (animationDrawable.isRunning()) {
animationDrawable.stop();
}
}
animationDrawable = new AnimationDrawable();
setImageDrawable(animationDrawable);
if (resourceIds != null && resourceIds.size() > 0) {
for (int resourceId : resourceIds) {
animationDrawable.addFrame(getResources().getDrawable(resourceId), 100);
}
}
startGif();
}
public void startGif() {
animationDrawable.start();
}
public void stopGif() {
animationDrawable.stop();
}
}
现在我们的自定义属性已经可以使用了,我们可以尝试一下
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.gifviewmvvm.GifView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:gifResourceIds="@array/gifResourceIds"
/>
</LinearLayout>
如果图片可以动了就成功了
4. Viewmodel
现在来写viewmodel,在我们这个viewmodel只需要实现一个简单的设置资源的功能
public class GifViewModel extends ViewModel {
private MutableLiveData<Integer> gifResourceIds = new MutableLiveData<>();
public GifViewModel(){
setGifResourceIds();
}
public void setGifResourceIds(){
gifResourceIds.setValue(R.array.gifResourceIds);
}
public MutableLiveData<Integer> getGifResourceIds() {
return gifResourceIds;
}
}
5. MainActvity
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
viewModel = new ViewModelProvider(this).get(GifViewModel.class);
binding.setViewModel(viewModel);
binding.setLifecycleOwner(this);
现在所有内容都完成了!可以试试在xml使用双向数据绑定了
<com.example.gifviewmvvm.GifView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:gifResourceIds="@{viewModel.gifResourceIds}"
/>
可以尝试再在页面做一个按钮,实现点击按钮更换图片资源的效果,体会mvvm框架的便利。
四、总结
经过这次练习感觉对于MVVM框架更加清晰了一些,不过这个自定义控件是继承自imageview的,所以写起来并不需要measure,layout,draw的流程,降低了难度,以后可以试试更复杂的版本。
网友评论