写在最前面:这儿所指的自界说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的流程,降低了难度,今后能够试试更复杂的版本。