计算位图平铺高度,以便不裁剪最后一项

Calculate Bitmap tile height so last item not cropped

问题是创建将按 X 轴拉伸的平铺背景,这样最后一个元素将不会被裁剪。

我想要的(计算每个 "dot" 图像的高度,这样最后一个图块就不会被裁剪):

我有(看到最后一个元素被裁剪):

应该有一种方法可以创建自定义 class 扩展 ImageViewBitmap 并手动执行该计算。但是我真的无法获得有关如何正确执行此操作的任何信息。

bg_dots.xml:

<?xml version="1.0" encoding="utf-8"?>
<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/ic_dot"
    android:tileMode="repeat"/>

以及它的使用方式:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/bg_dots" />

编辑:

感谢@paulina_glab 的文章和提供的信息,我已经能够创建自定义视图来绘制重复的 tile drawable。但是计算位图的实际 width\height 以适应屏幕高度仍然存在问题。

我所做的计算涉及浮点数到整数的舍入,这会带来增量。位图需要整数作为宽度和高度,但计算表明它应该是浮点值才能完全适合屏幕。

这是我带来的:

public class DotsView extends View {
private Rect rect;
private Bitmap mBitmap;
private BitmapDrawable mDrawable;

private int screenWidth;
private int screenHeight;
private int calculatedHeight;
private int calculatedWidth;

public DotsView(Context context) {
    super(context);
    init();
}

public DotsView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public DotsView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init(){
    Resources res = getResources();

    //get window specs
    WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);


    //get bitmap from resource image
    mBitmap =  BitmapFactory.decodeResource(res, R.drawable.ic_dot);

    //define width and height of custom view after all modifications
    screenWidth = size.x;
    screenHeight = size.y;

    //calculate proportion of height\width so the last element wont be cropped
    calculatedHeight = calculateHeight();
    calculatedWidth = calculateWidth();
    mBitmap = Bitmap.createScaledBitmap(mBitmap, calculatedWidth, calculatedHeight, false);

    //create drawable from customized bitmap
    mDrawable = new BitmapDrawable(res, mBitmap);
    //set it to be repeating tile
    mDrawable.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);

    //define borders of drawable to de drawn in
    rect = new Rect(0, 0, screenWidth, calculatedHeight);
    mDrawable.setBounds(rect);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mDrawable.draw(canvas);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(screenWidth, calculatedHeight);
    setMeasuredDimension(screenWidth, calculatedHeight);
}

private int calculateWidth(){
    /*such calculation brings delta from conversion float to int. This delta doesn't let to draw
    * exactly 25 dots. Any workarounds? */
    return (int)(mBitmap.getWidth() / ((float)(mBitmap.getWidth() * 25) / screenWidth)); //draw around 25 dots
}

private int calculateHeight(){
    return (int)(screenHeight * 0.07); //make it around 7 percents of screen
}
}

请看calculateWidth()方法。

它看起来像什么(由于三角洲,仍然有裁剪点):

我不知道如何通过 bitmap/xml 做你想做的事,但是(如你所想)我可以建议创建自定义 View class.

View.onDraw() 方法让您定义自定义绘图过程 并在

View.onSizeChanged() 您可以计算正确的 count/height 个点

您可以使用 Drawable.draw().

绘制 单点

比你得到 一个自定义的 wiget,你可以把它放在一些布局容器中(比如 TextView,而不是 android:background ).


我写了一个 article about drawing custom views - 那里描述的案例更复杂,但你可以看到我上面提到的每个方法的用法。例如,您可以跳过有关 Path 或属性的段落。

这是一个 official tutorial。这可能对您也有用,但它非常笼统。

如果你想在Android系统中进行自定义绘图,你应该只使用Android框架,测量和布局等事情会容易得多。

return (int)(screenHeight * 0.07); //make it around 7 percents of screen

如果您 "guess" onMeasure 中的高度,您并没有真正测量视图。你想要 25 个点,沿着视图的宽度分布,所以这就是你应该做的。

  1. 摆脱 WindowManagerDisplay 等。Android 现在有分屏模式,有软导航栏...屏幕大小并不重要。
  2. 使用 Android 框架。

总是相同的过程:测量、布局、绘制。

正在测量

你在onMeasure,所以测量widthMeasureSpec 包含您需要的信息——在本例中为可用宽度。用 MeasureSpec.getSize(widthMeasureSpec).

阅读

现在你想要沿着这个宽度有 25 个点并分别有一个高度...所以你需要做的就是除以 width / 25 然后你就会知道你的视图应该有多高。

通过告诉框架你想要多高来完成测量!

setMeasuredDimension(availableWidth, neededHeight);

布局

如果有什么改变..不管是什么原因...在这里我们得到了视图的最终大小。调整可绘制对象、缩放图像、计算边界...

尽量不要做太复杂的工作,因为这可能会被调用很多

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);

    // called after measuring. here we know the actual size of our view

    // assuming it's a square, else do some Math.min to get a valid size
    int size = bottom - top;

    // set the size for the drawable, maybe scale up / down, maybe center...
    mDrawable.setBounds(left, top, left + size, top + size);
}

绘图

您可以通过创建一些 Paint 并在 canvas 上创建一些 drawCircle(...) 来完全自定义绘图,或者您可以只使用一些 Drawable。点的画法有无限可能

你想要它 25 次所以添加一个循环并确保它沿着 x 轴移动。大功告成!


由于要掌握的内容很多,这里有一个简单的例子,有一个基本的可绘制对象。我还写了一篇关于 view basics 的文章,其中包含有关测量和布局的更多信息。尝试看看它是如何工作的(我自己喜欢在 Android Studio 中使用布局预览来测试我的观点!)。一旦您了解了如何测量和布局您的视图,剩下的就是正确缩放您的位图。

public class DotsView extends View {
    private static final int NUM_DOTS = 25;

    private Drawable mDrawable;

    public DotsView(Context context) {
        super(context);
        init();
    }

    public DotsView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public DotsView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mDrawable = new DotDrawable();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // measure where you're supposed to measure: in onMeasure

        // get all of the width
        // this will work with match_parent, as well as with a width of 32dp
        int availableWidth = MeasureSpec.getSize(widthMeasureSpec);

        // NUM_DOTS == 25, or whatever, use a constant or read into attributes
        int neededHeight = (int) (availableWidth / (float) NUM_DOTS);

        // apply the measure dimension to the view
        setMeasuredDimension(availableWidth, neededHeight);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        // called after measuring. here we know the actual size of our view

        // assuming it's a square, else do some Math.min to get a valid size
        int size = bottom - top;

        // set the size for the drawable, maybe scale up / down, maybe center...
        mDrawable.setBounds(left, top, left + size, top + size);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // get the width of this view and calculate the width used per dot
        float widthPerDot = getWidth() / (float) NUM_DOTS;

        // draw every single dot where it belongs.
        for (int i = 0; i < NUM_DOTS; i++) {
            // just redraw the same drawable and move it along the x axis
            mDrawable.getBounds().offsetTo((int) ((i * widthPerDot)), 0);

            // finally just draw the dot
            mDrawable.draw(canvas);
        }
    }
    public class DotDrawable extends android.graphics.drawable.Drawable {

        Paint mPaint;

        DotDrawable() {
            mPaint = new Paint();
            mPaint.setColor(Color.RED);
            mPaint.setStyle(Paint.Style.FILL);
        }

        @Override
        public void draw(Canvas canvas) {
            Rect bounds = getBounds();
            canvas.drawCircle(bounds.centerX(), bounds.centerY(), Math.min(bounds.width(), bounds.height()) / 2, mPaint);
        }

        @Override
        public void setAlpha(int alpha) {

        }

        @Override
        public void setColorFilter(ColorFilter cf) {

        }

        @Override
        public int getOpacity() {
            return 0;
        }
    }
}