How to change color of vector drawable path on but

2020-01-28 02:52发布

With the new android support update, vector drawables get backward compatibility. I have a vector image with various paths. I want the color of the paths to change on click of a button or programmatically based on an input value. Is it possible to access the name parameter of the vector path? And then change the color.

8条回答
你好瞎i
2楼-- · 2020-01-28 02:56

You can change the color of individual path at runtime, without using reflection.

VectorMaster introduces dynamic control over vector drawables. Each and every aspect of a vector drawable can be controlled dynamically (via Java instances), using this library.

Just add the following dependency in your app's build.gradle

dependencies {
    compile 'com.sdsmdg.harjot:vectormaster:1.0.9'
}

In your case you need a simple color change:

Vector example: your_vector.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
<path
    android:name="outline"
    android:pathData="M20.84,4..."
    android:strokeColor="#5D5D5D"
    android:fillColor="#00000000"
    android:strokeWidth="2"/>

XML:

<com.sdsmdg.harjot.vectormaster.VectorMasterView
    android:id="@+id/your_vector"
    android:layout_width="150dp"
    android:layout_height="150dp"
    app:vector_src="@drawable/your_drawable" />

Java:

VectorMasterView heartVector = (VectorMasterView) 
findViewById(R.id.your_drawable);

// find the correct path using name
PathModel outline = heartVector.getPathModelByName("outline");

// set the stroke color
outline.setStrokeColor(Color.parseColor("#ED4337"));

// set the fill color (if fill color is not set or is TRANSPARENT, then no fill is drawn)
outline.setFillColor(Color.parseColor("#ED4337"));

From: https://github.com/harjot-oberai/VectorMaster, licensed under MIT.

You have now full control over vector drawables.

查看更多
霸刀☆藐视天下
3楼-- · 2020-01-28 02:58

You can achieve this without a library, and combine Data Binding with Kotlin.

ButtonBinding.kt

    @SuppressLint("PrivateResource")
    @BindingAdapter("drawable:start", "drawable:color", "button:isClicked", requireAll = false)
    @JvmStatic
    fun Button.setDrawableStartColor(@DrawableRes start: Int, @ColorRes color: Int, isClicked: Boolean) {
        this.apply btn@{
        context.apply ctx@{
            val drawable = ContextCompat
                    .getDrawable(this, start)
                    ?.apply {
                        if (isClicked) {
                            setColorFilter(
                                    ContextCompat.getColor(this@ctx, color),
                                    PorterDuff.Mode.SRC_ATOP)
                        }
                    }

            setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
        }
        }
    }

You can directly use setCompoundDrawablesWithIntrinsicBounds or setCompoundDrawables( but you have to define the current intrinsic bound, check out this article here

my_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:drawable="http://schemas.android.com/apk/res-auto">

    <data>

        <import type="your.package.R"/>

        <variable
            name="actionListener"
            type="your.package.mvvmV2.account.profile.ProfileActionListener"/>

        <variable
            name="profileResponse"
            type="your.package.data.model.ProfileResponse"/>

        <variable
            name="viewModel"
            type="your.package.mvvmV2.home.HomeViewModel"/>
    </data>
            <Button
                drawable:color="@{R.color.white}"
                drawable:start="@{R.drawable.ic_person_blue}"
                android:id="@+id/buttonProfile"
</layout>

For setup click listener, you can do this following as well:

YourFragment.kt

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        mViewBinding = AccountFragmentV2Binding
                .inflate(inflater, container, false)
                .apply {
                    viewModel = mViewModel // also, assign the correct view model to your xml
                    actionListener = this@AccountFragment
                    setLifecycleOwner(this@AccountFragment) // don't forget this
                }

        return mViewBinding.root
    }

    override fun onProfileClicked(v: View) {
        mViewBinding.apply {
            mViewModel.let {
                // change and store the state, but don't forget to reset to default
                it.clickState[R.drawable.v2_ic_person_blue] = v.isPressed
            }
            executePendingBindings() // update ui directly, without delays
        }
    }

ViewModel.kt

val clickState = ObservableArrayMap<Int, Boolean>()

    init {
        clickState[R.drawable.ic_aktivitas_blue] = false
        clickState[R.drawable.ic_person_blue] = false
        clickState[R.drawable.ic_bookmark_blue] = false
    }

Define the binding adapter with integer resource instead of using @color/ or @drawable. If you prefer using @ annotation (e.g: drawable:start="@{@drawable/id}") on the XML.

Change the data binding like this

fun Button.setDrawable(start: Drawable, color: Color) {
// do the conversion to int id, it will be a mess. that's why I stick to receive int id from resource instead
}

Also, don't forget to set up this from @aminography

Important Point in Using Android Vector Drawable When you are using an android vector drawable and want to have backward compatibility for API below 21, add the following codes to

In app level build.gradle:

android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
}

In Application class:

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
    }

}

Thanks to this answer here, @Varun here explained any other alternative, the original answer for my case here

查看更多
乱世女痞
4楼-- · 2020-01-28 02:59

There are several ways of doing the same stuff, but this works for both Vector Drawables as well as SVG (Local/Network).

imageView.setColorFilter(ContextCompat.getColor(context, 
R.color.headPink), android.graphics.PorterDuff.Mode.SRC_IN);

(change R.color.headPink with color of your choice)

查看更多
Juvenile、少年°
5楼-- · 2020-01-28 03:07

You can use this method to change color in lower API to change vector color in fragment

int myVectorColor = ContextCompat.getColor(getActivity(), R.color.colorBlack);
                    myButton.getIcon().setColorFilter(myVectorColor, PorterDuff.Mode.SRC_IN);

in place of getActivity you should use MainActivity.this for changing vector color in activity

查看更多
迷人小祖宗
6楼-- · 2020-01-28 03:09

Use this to change a path color in your vector drawable

VectorChildFinder vector = new VectorChildFinder(this, R.drawable.my_vector, imageView);

VectorDrawableCompat.VFullPath path1 = vector.findPathByName("path1");
path1.setFillColor(Color.RED); 

Library is here: https://github.com/devsideal/VectorChildFinder

查看更多
小情绪 Triste *
7楼-- · 2020-01-28 03:09

As stated by @Eyal in this post https://stackoverflow.com/a/32007436/4969047

You cannot change the color of individual path at runtime. Looking at the source code of VectorDrawableCompat, the only method to expose the inner element by name is getTargetByName which is present in inner private state class VectorDrawableCompatState of VectorDrawableCompat.

Since it is a package private (default) - you can't use it (unless you use reflection).

查看更多
登录 后发表回答