在Android中进度条通常都是使用ClipDrawable绘制实现,但是通常情况下ClipDrawable的默认绘制方向都是水平方向,从左到右进行进度绘制,如果要实现从右到左,重上到下,或从下到上的进度绘制的话,除非重新创建一个新的ClipDrawable才能实现自己需要的方向,如果要修改现有的ClipDrawable绘制方向及开始位置,公有的方法时没有的,这个问题今天我折腾了半天时间也没有找到对应的方法,在不断的测试下,找到一种目前可用的方法实现,那就是使用JAVA的反射来实现。具体如下代码:
/**
* 使用反射方法设置ClipDrawable的orientation和gravity字段
* @param clipDrawable 裁剪Drawable
* @param orientation 0 水平方向,1 垂直方向
*/
private void setClipOrientation(ClipDrawable clipDrawable,int orientation){
if(clipDrawable!=null){
// 使用反射获取ClipDrawable的ClipState字段
try {
// 获取mState字段
Field clipStateField = ClipDrawable.class.getDeclaredField("mState");
clipStateField.setAccessible(true); //设置可访问
// 获取ClipState实例对象
Object clipState = clipStateField.get(clipDrawable);
//从clipState实例对象中获取orientation字段
Field orientationField = clipState.getClass().getDeclaredField("mOrientation");
//从clipState实例对象中获取gravity字段
Field gravityField= clipState.getClass().getDeclaredField("mGravity");
orientationField.setAccessible(true); //设置可访问
gravityField.setAccessible(true); //设置可访问
int clipOrientation= (int) orientationField.get(clipState); //获取裁剪方向
int gravity =(int) gravityField.get(clipState); //获取裁剪的开始位置
if(orientation==0){ //如果是水平方向
if(clipOrientation!=ClipDrawable.HORIZONTAL) //如果裁剪方向不是水平方向
orientationField.set(clipState, ClipDrawable.HORIZONTAL); //设置裁剪方向为水平方向
if(gravity!=Gravity.LEFT) //如果裁剪的开始位置不是左
gravityField.set(clipState, Gravity.LEFT); //设置裁剪的开始位置为左
}else{
if(clipOrientation!=ClipDrawable.VERTICAL) //如果裁剪的方向不是垂直方向
orientationField.set(clipState, ClipDrawable.VERTICAL); //设置裁剪方向为垂直方向
if(gravity!=Gravity.BOTTOM) //如果裁剪的开始位置不是下
gravityField.set(clipState, Gravity.BOTTOM); //设置裁剪的开始位置为下
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用这个方法直接可用将绘制方向调整为水平从左到右绘制会垂直从下到上绘制,其它绘制位置只需要修改 gravityField.set(clipState, Gravity.BOTTOM);方法即可实现自己需要的绘制开始位置。
下面在提供一个继承ClipDrawable的重构类,在类里面通过反射将Orientation和Gravity字段开放了出来,便于用户自由修改裁剪方向及位置。类代码如下:
package com.racer.smart.pro.view;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.Drawable;
import java.lang.reflect.Field;
/**
* 自定义ClipDrawable,替代原生的ClipDrawable。
* 可修改裁剪的方向和裁剪的开始位置(利用反射实现)。
*/
public class CustomClipDrawable extends ClipDrawable {
private Object clipState=null;
private Field orientationField=null;
private Field gravityField=null;
public CustomClipDrawable(Drawable drawable, int gravity, int orientation) {
super(drawable, gravity, orientation);
}
public void setOrientation(int orientation){
if(orientationField==null)
reflexInit();
if(orientationField!=null){
try {
//设置orientation
orientationField.set(clipState,orientation);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
public void setGravity(int gravity){
if(gravityField==null)
reflexInit();
if(gravityField!=null){
try {
//设置gravity
gravityField.set(clipState,gravity);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
public int getOrientation() {
int clipOrientation=-1;
if(orientationField==null)
reflexInit();
if(orientationField!=null){
try {
clipOrientation= (int) orientationField.get(clipState); //获取裁剪方向
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return clipOrientation;
}
public int getGravity() {
int gravity=-1;
if(gravityField==null)
reflexInit();
if(gravityField!=null){
try {
gravity= (int) gravityField.get(clipState); //获取裁剪方向
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return gravity;
}
/**反射初始化。拿到clipState实例对象,gravity和orientation字段*/
private void reflexInit(){
try {
// 获取mState字段
Field clipStateField = ClipDrawable.class.getDeclaredField("mState");
clipStateField.setAccessible(true); //设置可访问
// 获取ClipState实例对象
clipState = clipStateField.get(this);
//从clipState实例对象中获取gravity字段
orientationField = clipState.getClass().getDeclaredField("mOrientation");
//从clipState实例对象中获取gravity字段
gravityField= clipState.getClass().getDeclaredField("mGravity");
} catch (NoSuchFieldException|IllegalAccessException e) {
e.printStackTrace();
}
}
}
在自己代码中使用该类时请务必将package com.racer.smart.pro.view;
包名称修改成自己的包名称。
注:这两天我在compileSdk 34下获取失败了,估计是新版有什么限制,前面是在32本版下都正常
针对上面提到的在compileSdk 34下失败的问题,今天找到了第三方库的解决方法,使用JNI的本地方法来绕过隐藏API的限制。只需要在工程中将库英勇到工程即可,使用如下:
1、在根build.gradle:
allprojects {
repositories {
[..]
jcenter()
maven { url "https://jitpack.io" }
}
}
2、在工程build.gradle中添加:
dependencies {
implementation 'com.github.ChickenHook:RestrictionBypass:2.2'
}
做完这些将库引用到工程即可了,原本的反射代码不做任何更改,直接即可使用。