第一,Drawable概念
鸿阳大神的博客,大家可以参考下
这篇博客也可以参考下
Drawable就是一个可画的对象,表示一种可以在Canvas上进行绘制的抽象的概念,其可能是一张(BitmapDrawable),
也可能是一个图形(ShapeDrawable),还有可能是一个图层(LayerDrawable),我们根据画图的需求,创建相应的可画对象,
就可以将这个可画对象当作一块“画布(Canvas)”,在其上面操作可画对象,并最终将这种可画对象显示在画布上,有点类似于“内存画布“。
ImageView imageView = new ImageView(this);
Drawable drawable = getResources().getDrawable(R.drawable.avft);
imageView.setImageDrawable(drawable);
Drawable 并不是一张图片,而是绘制图片到canvas的工具。
Drawable有三个重要的方法,第一个是draw();这是个抽象方法:public abstract void draw( Canvas canvas);这是个抽象方法,具体逻辑在子类中实现。
第二个:
/**
812 * Specify the level for the drawable. This allows a drawable to vary its
813 * imagery based on a continuous controller, for example to show progress
814 * or volume level.
815 *
816 * <p>If the new level you are supplying causes the appearance of the
817 * Drawable to change, then it is responsible for calling
818 * {@link #invalidateSelf} in order to have itself redrawn, <em>and</em>
819 * true will be returned from this function.
820 *
821 * @param level The new level, from 0 (minimum) to 10000 (maximum).
822 *
823 * @return Returns true if this change in level has caused the appearance
824 * of the Drawable to change (hence requiring an invalidate), otherwise
825 * returns false.
826 */
827 public final boolean setLevel(@IntRange(from=0,to=10000) int level) {
828 if (mLevel != level) {
829 mLevel = level;
830 return onLevelChange(level);
831 }
832 return false;
833 }
834
官方的解释翻译过来就是:指定可绘制对象的级别。这允许可绘制对象基于连续控制器改变其图像。例如显示进度或音量级别。
可以理解为设置图层,值为0~10000;当然了,有set方法,就有get方法
我们先看下代码
package com.example.lsn5;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* @author writing
* @time 2019/12/14 16:13
* @note
*/
public class RevealDrawable extends Drawable {
private Drawable selectedImage;
private Drawable unSelectedImage;
public RevealDrawable(Drawable selectedImage, Drawable unSelectedImage) {
this.selectedImage = selectedImage;
this.unSelectedImage = unSelectedImage;
}
@Override
public void draw(@NonNull Canvas canvas) {
Rect bounds = getBounds();
Rect temp = new Rect();
Gravity.apply(Gravity.LEFT,bounds.width()/2,bounds.height(),bounds,temp);
canvas.save();
canvas.clipRect(temp);
selectedImage.draw(canvas);
canvas.restore();
Gravity.apply(Gravity.RIGHT,bounds.width()/2,bounds.height(),bounds,temp);
canvas.save();
canvas.clipRect(temp);
unSelectedImage.draw(canvas);
canvas.restore();
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
selectedImage.setBounds(bounds);
unSelectedImage.setBounds(bounds);
}
@Override
public int getIntrinsicHeight() {
return unSelectedImage.getIntrinsicHeight();
}
@Override
public int getIntrinsicWidth() {
return unSelectedImage.getIntrinsicWidth();
}
@Override
public void setAlpha(int i) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return 0;
}
}
核心代码就是draw了,but,我们只需要一行~~~~setAlpha、setColorFilter、getOpacity、draw这几个方法是必须实现的,不过除了draw以为,其他都很简单。getIntrinsicWidth、getIntrinsicHeight主要是为了在View使用wrap_content的时候,提供一下尺寸,默认为-1可不是我们希望的。setBounds就是去设置下绘制的范围。
MainActivity
package com.example.lsn5;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = findViewById(R.id.image);
RevealDrawable drawable = new RevealDrawable(getResources().getDrawable(R.drawable.avft),getResources().getDrawable(R.drawable.avft_active));
imageView.setImageDrawable(drawable);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:id="@+id/image"
android:background="@color/colorPrimaryDark"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
我们看下运行效果
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs4GazQmZk1WZqZ0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLygTO2UjMzcTM0EjMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
看下原来的两张图片,我们分别截取左右两部分
滑动变色
我们在上面的基础上继续完善我们的代码
package com.dn_alan.myapplication;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class GallaryHorizonalScrollView extends HorizontalScrollView{
private LinearLayout container;
private int w;//单张图的宽度
public GallaryHorizonalScrollView(Context context) {
super(context);
init();
}
public GallaryHorizonalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GallaryHorizonalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
container = new LinearLayout(getContext());
container.setLayoutParams(lp);
addView(container);
}
/**
* 渐变图片
*/
private void reveal() {
//得到hzw滑出去的横向距离,根据滚动的距离计算出当前滚动到了哪一张图片
int scrollX = getScrollX();
//找到两张渐变图片的下标 ----左,右
//处在中间位置的两张图片
int indexLeft = scrollX / w;
int indexRight = indexLeft + 1;
//偏移了多少
float trans = scrollX % w;
int levelLeft = (int) (5000 - Math.abs(trans / w * 5000));
int levelRight = levelLeft + 5000;
Log.e("---------------","left = " + levelLeft + " right = " + levelRight);
int count = container.getChildCount();
for (int i = 0; i < count; i++) {
ImageView iv = (ImageView) container.getChildAt(i);
if(i == indexLeft){
iv.setImageLevel(levelLeft);
}else if(i == indexRight){
iv.setImageLevel(levelRight);
}else{
iv.setImageLevel(0);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//获取一个子View,因为所有的子View的宽高都是一样的。
View first = container.getChildAt(0);
//获取子View 的宽,根据滚动距离,计算滑动到了哪个View
w = first.getWidth();
int padding = getWidth() / 2 - w / 2;
//给LinearLayout和hzw之间设置边框距离。
container.setPadding(padding,0,padding,0);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
reveal();
}
public void addViews(){
container.removeAllViews();
for (int i = 0; i < mImgIds.length; i++) {
container.addView(getRevealView(i));
}
ImageView childAt = (ImageView) container.getChildAt(0);
childAt.setImageLevel(5000);
}
private View getRevealView(int i) {
ImageView iv = new ImageView(getContext());
Drawable d1 = getResources().getDrawable(mImgIds[i]);
Drawable d2 = getResources().getDrawable(mImgIdsActive[i]);
RevealDrawable rd = new RevealDrawable(d1,d2, RevealDrawable.HORIZONTAL);
iv.setImageDrawable(rd);
return iv;
}
private int[] mImgIds = new int[]{ //7个
R.drawable.avft,
R.drawable.box_stack,
R.drawable.bubble_frame,
R.drawable.bubbles,
R.drawable.bullseye,
R.drawable.circle_filled,
R.drawable.circle_outline,
R.drawable.avft,
R.drawable.box_stack,
R.drawable.bubble_frame,
R.drawable.bubbles,
R.drawable.bullseye,
R.drawable.circle_filled,
R.drawable.circle_outline
};
private int[] mImgIdsActive = new int[]{
R.drawable.avft_active, R.drawable.box_stack_active, R.drawable.bubble_frame_active,
R.drawable.bubbles_active, R.drawable.bullseye_active, R.drawable.circle_filled_active,
R.drawable.circle_outline_active,
R.drawable.avft_active, R.drawable.box_stack_active, R.drawable.bubble_frame_active,
R.drawable.bubbles_active, R.drawable.bullseye_active, R.drawable.circle_filled_active,
R.drawable.circle_outline_active
};
}
package com.dn_alan.myapplication;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class RevealDrawable extends Drawable {
private Drawable d1,d2;
private Rect mRect = new Rect();
private int mOrientation;
public static final int HORIZONTAL = 1;
public static final int VERTICAL = 2;
public RevealDrawable(Drawable d1, Drawable d2, int orientation){
this.d1 = d1;
this.d2 = d2;
this.mOrientation = orientation;
}
@Override
public int getIntrinsicWidth() {
return Math.max(d1.getIntrinsicWidth(),d2.getIntrinsicWidth());
}
@Override
public int getIntrinsicHeight() {
return Math.max(d1.getIntrinsicHeight(),d2.getIntrinsicHeight());
}
/**
* 制提供了画布上下文,那么就还需要提供一个可绘制的区域,下面方法就是用来指定绘制的区域。
* Drawable在绘制调用draw函数之前必须要先指定绘制的区域,这个区域也是Canvas中要绘制的区域。
* 一旦用户改变了绘制区域时会激发onBoundsChange方法,派生类可以重载onBoundsChange来实现区域变更的处理。
*/
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
d1.setBounds(bounds);
d2.setBounds(bounds);
}
//当调用ImageView的setImageLevel()方法时候,会执行这个方法
//层级发生改变的话可以在这个方法里做些处理,level的值为0-10000
/**
* public final boolean setLevel(int level)
* public final int getLevel()
* 你可以用这两个的方法来设置显示的级别,以便进行一些绘制时的区间和条件控制,这个属性并不是所有Drawable派生类都能用到,
* 如果设置有变化则会调用onLevelChange,派生类可以重载onLevelChange来实现级别变化的更新处理:
*/
@Override
protected boolean onLevelChange(int level) {
invalidateSelf();
return super.onLevelChange(level);
}
@Override
public void draw(@NonNull Canvas canvas) {
int level = getLevel();
int gravity = level < 5000 ? Gravity.LEFT : Gravity.RIGHT;
float ratio = Math.abs(level / 5000f - 1);
//得到当前自身Drawable的矩形区域,其长和宽就是getIntrinsicWidth和getIntrinsicHeight方法返回的值
Rect bounds = getBounds();
int w = bounds.width();
int h = bounds.height();
if(mOrientation == HORIZONTAL){
w *= ratio;
}else if(mOrientation == VERTICAL){
h *= ratio;
}
Gravity.apply(
//从左边扣还是从右边扣
gravity,
//目标矩形宽
w,
//目标矩形高
h,
//被扣对象
bounds,
//承载对象
mRect
);
canvas.save();
//裁剪;裁剪了我们想要的绘制区域,矩形来确认需要剪裁的位置
canvas.clipRect(mRect);
//把图形画到传入的 Canvas 上
d1.draw(canvas);
canvas.restore();
w = bounds.width();
h = bounds.height();
gravity = level > 5000 ? Gravity.LEFT : Gravity.RIGHT;
if(mOrientation == HORIZONTAL){
w -= w * ratio;
}else if(mOrientation == VERTICAL){
h -= h * ratio;
}
Gravity.apply(
//从左边扣还是从右边扣
gravity,
//目标矩形宽
w,
//目标矩形高
h,
//被扣对象
bounds,
//承载对象
mRect
);
canvas.save();
canvas.clipRect(mRect);
d2.draw(canvas);
canvas.restore();
}
@Override
public void setAlpha(int i) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.UNKNOWN;
}
}
package com.dn_alan.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
private ImageView iv;
private int[] mImgIds = new int[]{ //7个
R.drawable.avft,
R.drawable.box_stack,
R.drawable.bubble_frame,
R.drawable.bubbles,
R.drawable.bullseye,
R.drawable.circle_filled,
R.drawable.circle_outline,
R.drawable.avft,
R.drawable.box_stack,
R.drawable.bubble_frame,
R.drawable.bubbles,
R.drawable.bullseye,
R.drawable.circle_filled,
R.drawable.circle_outline
};
private int[] mImgIds_active = new int[]{
R.drawable.avft_active, R.drawable.box_stack_active, R.drawable.bubble_frame_active,
R.drawable.bubbles_active, R.drawable.bullseye_active, R.drawable.circle_filled_active,
R.drawable.circle_outline_active,
R.drawable.avft_active, R.drawable.box_stack_active, R.drawable.bubble_frame_active,
R.drawable.bubbles_active, R.drawable.bullseye_active, R.drawable.circle_filled_active,
R.drawable.circle_outline_active
};
public Drawable[] revealDrawables;
protected int level = 5000;
private GallaryHorizonalScrollView hzv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initView() {
hzv = (GallaryHorizonalScrollView) findViewById(R.id.hsv);
}
private void initData() {
revealDrawables = new Drawable[mImgIds.length];
for (int i = 0; i < mImgIds.length; i++) {
RevealDrawable rd = new RevealDrawable(
getResources().getDrawable(mImgIds[i]),
getResources().getDrawable(mImgIds_active[i]),
RevealDrawable.HORIZONTAL);
revealDrawables[i] = rd;
}
hzv.addViews();
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<com.dn_alan.myapplication.GallaryHorizonalScrollView
android:id="@+id/hsv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="#AA444444"
android:scrollbars="none"
/>
</LinearLayout>
我们看下效果
我们看下图片资源,两套图片