美文网首页
Android 自定义属性与数据绑定(以一个自定义简单GIF控件

Android 自定义属性与数据绑定(以一个自定义简单GIF控件

作者: 头秃到底 | 来源:发表于2024-02-29 19:53 被阅读0次

写在最前面:这里所指的自定义GIF控件指的是一个继承自imageview的、使用帧动画将一系列图片播放出来的控件,并不是读取GIF图片播放的控件

一、实现内容

  1. 创建一个自定义控件,可以播放图片资源达成类似GIF的效果

  2. 在自定义控件中使用自定义属性

  3. 使用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的流程,降低了难度,以后可以试试更复杂的版本。

相关文章

网友评论

      本文标题:Android 自定义属性与数据绑定(以一个自定义简单GIF控件

      本文链接:https://www.haomeiwen.com/subject/mimqzdtx.html