/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v17.leanback.graphics; import android.support.annotation.RequiresApi; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.NonNull; import android.util.IntProperty; import android.util.Property; /** * Subclass of {@link Drawable} that can be used to draw a bitmap into a region. Bitmap * will be scaled to fit the full width of the region and will be aligned to the top left corner. * Any region outside the bounds will be clipped during {@link #draw(Canvas)} call. Top * position of the bitmap can be controlled by {@link #setVerticalOffset(int)} call or * {@link #PROPERTY_VERTICAL_OFFSET}. */ public class FitWidthBitmapDrawable extends Drawable { static class BitmapState extends Drawable.ConstantState { Paint mPaint; Bitmap mBitmap; Rect mSource; final Rect mDefaultSource = new Rect(); int mOffset; BitmapState() { mPaint = new Paint(); } BitmapState(BitmapState other) { mBitmap = other.mBitmap; mPaint = new Paint(other.mPaint); mSource = other.mSource != null ? new Rect(other.mSource) : null; mDefaultSource.set(other.mDefaultSource); mOffset = other.mOffset; } @NonNull @Override public Drawable newDrawable() { return new FitWidthBitmapDrawable(this); } @Override public int getChangingConfigurations() { return 0; } } final Rect mDest = new Rect(); BitmapState mBitmapState; boolean mMutated = false; public FitWidthBitmapDrawable() { mBitmapState = new BitmapState(); } FitWidthBitmapDrawable(BitmapState state) { mBitmapState = state; } @Override public ConstantState getConstantState() { return mBitmapState; } @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { mBitmapState = new BitmapState(mBitmapState); mMutated = true; } return this; } /** * Sets the bitmap. */ public void setBitmap(Bitmap bitmap) { mBitmapState.mBitmap = bitmap; if (bitmap != null) { mBitmapState.mDefaultSource.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); } else { mBitmapState.mDefaultSource.set(0, 0, 0, 0); } mBitmapState.mSource = null; } /** * Returns the bitmap. */ public Bitmap getBitmap() { return mBitmapState.mBitmap; } /** * Sets the {@link Rect} used for extracting the bitmap. */ public void setSource(Rect source) { mBitmapState.mSource = source; } /** * Returns the {@link Rect} used for extracting the bitmap. */ public Rect getSource() { return mBitmapState.mSource; } /** * Sets the vertical offset which will be used for drawing the bitmap. The bitmap drawing * will start the provided vertical offset. * @see #PROPERTY_VERTICAL_OFFSET */ public void setVerticalOffset(int offset) { mBitmapState.mOffset = offset; invalidateSelf(); } /** * Returns the current vertical offset. * @see #PROPERTY_VERTICAL_OFFSET */ public int getVerticalOffset() { return mBitmapState.mOffset; } @Override public void draw(Canvas canvas) { if (mBitmapState.mBitmap != null) { Rect bounds = getBounds(); mDest.left = 0; mDest.top = mBitmapState.mOffset; mDest.right = bounds.width(); Rect source = validateSource(); float scale = (float) bounds.width() / source.width(); mDest.bottom = mDest.top + (int) (source.height() * scale); int i = canvas.save(); canvas.clipRect(bounds); canvas.drawBitmap(mBitmapState.mBitmap, source, mDest, mBitmapState.mPaint); canvas.restoreToCount(i); } } @Override public void setAlpha(int alpha) { final int oldAlpha = mBitmapState.mPaint.getAlpha(); if (alpha != oldAlpha) { mBitmapState.mPaint.setAlpha(alpha); invalidateSelf(); } } /** * @return Alpha value between 0(inclusive) and 255(inclusive) */ @Override public int getAlpha() { return mBitmapState.mPaint.getAlpha(); } @Override public void setColorFilter(ColorFilter colorFilter) { mBitmapState.mPaint.setColorFilter(colorFilter); invalidateSelf(); } @Override public int getOpacity() { final Bitmap bitmap = mBitmapState.mBitmap; return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; } private Rect validateSource() { if (mBitmapState.mSource == null) { return mBitmapState.mDefaultSource; } else { return mBitmapState.mSource; } } /** * Property for {@link #setVerticalOffset(int)} and {@link #getVerticalOffset()}. */ public static final Property PROPERTY_VERTICAL_OFFSET; static { if (Build.VERSION.SDK_INT >= 24) { // use IntProperty PROPERTY_VERTICAL_OFFSET = getVerticalOffsetIntProperty(); } else { // use Property PROPERTY_VERTICAL_OFFSET = new Property(Integer.class, "verticalOffset") { @Override public void set(FitWidthBitmapDrawable object, Integer value) { object.setVerticalOffset(value); } @Override public Integer get(FitWidthBitmapDrawable object) { return object.getVerticalOffset(); } }; } } @RequiresApi(24) static IntProperty getVerticalOffsetIntProperty() { return new IntProperty("verticalOffset") { @Override public void setValue(FitWidthBitmapDrawable fitWidthBitmapDrawable, int value) { fitWidthBitmapDrawable.setVerticalOffset(value); } @Override public Integer get(FitWidthBitmapDrawable fitWidthBitmapDrawable) { return fitWidthBitmapDrawable.getVerticalOffset(); } }; } }