LayerDrawable: можно ли это сделать более эффективно?

У меня есть ImageView, полная ширина и ширина:высота = 3:1, назовем его 1200 x 400. Я хочу показать два Drawable в ImageView, оба из которых известны только во время выполнения. Один из рисунков должен быть помещен в ImageView в соответствии с «обрезкой по центру» (при условии, что рисуемый объект имеет ширину: высота> 3, что означает точное соответствие ширины и обрезку сверху и снизу), другой должен быть отцентрирован и масштабирован по индивидуальному фактору.

У меня есть код, который это делает, но мне он кажется излишне сложным; Я смог заставить его работать так, как хотел, когда я создал новый Bitmap из LayerDrawable, затем BitmapDrawable из него и снова установил желаемые границы для BitmapDrawable, хотя я уже установил границы для LayerDrawable - но эти границы просто игнорируется, если я не выполняю дополнительный шаг. Я бы предпочел сгенерировать LayerDrawable таким образом, чтобы можно было использовать его так, как он есть в .setImageDrawable(), но я не знаю, как это сделать. То, что делает Android, если я пытаюсь, не имеет для меня никакого смысла. Что я делаю не так?

Вот как я делаю LayerDrawable

double divide(int k, int n) {
    return ((double)k)/n;
}
int verticalPaddingForHorizontalFit(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight){
    // you want to draw a drawable into a view, with an exact match in the width. Then either [1] or [2]
    // [1] the view is taller than the drawable (i.e., height/width bigger for the view)
    //     --> method result is positive,
    //     and gives the amount of padding top and bottom
    // [2] the drawable is taller
    //     --> method result is negative,
    //         and gives (minus) the amount that needs to be clipped from top and bottom
    // such that the drawable is vertically centered
    double viewAspect     = divide(viewHeight,     viewWidth    );
    double drawableAspect = divide(drawableHeight, drawableWidth);
    return (int)Math.round(0.5 * viewWidth * (viewAspect - drawableAspect));
}
int[] paddingWhenCenteredAt(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight, double drawableScale, int centerX, int centerY){
    // scale the drawable with drawableScale, and put it into the view
    // such that the center of the drawable has coordinates (centerX, centerY)
    // return the padding needed as array of left, top, right, bottom, in that order
    // negative values indicating clipping instead of padding
    double w = drawableScale * drawableWidth;
    double h = drawableScale * drawableHeight;
    double left = centerX - 0.5*w;
    double right = viewWidth - (centerX + 0.5*w);
    double top = centerY - 0.5*h;
    double bottom = viewHeight - (centerY + 0.5*h);
    return new int[]{(int)Math.round(left), (int)Math.round(top), (int)Math.round(right), (int)Math.round(bottom)};
}
LayerDrawable makeLayerDrawable(Resources r, int outputWidth, int outputHeight, Bitmap bm1, Bitmap bm2){
    Drawable[] layers = new Drawable[2];
    BitmapDrawable bmd1 = new BitmapDrawable(r, bm1);
    int width1  = bmd1.getIntrinsicWidth();
    int height1 = bmd1.getIntrinsicHeight();
    layers[0]   = bmd1;
    BitmapDrawable bmd2 = new BitmapDrawable(r, bm2);
    int width2  = bmd2.getIntrinsicWidth();
    int height2 = bmd2.getIntrinsicHeight();
    layers[1]   = bmd2;
    LayerDrawable result = new LayerDrawable(layers);
    int vPad = verticalPaddingForHorizontalFit(outputWidth, outputHeight, width1, height1);
    result.setLayerInset(0, 0, vPad, 0, vPad);
    int[] ltrb = paddingWhenCenteredAt(outputWidth, outputHeight, width2, height2, 0.5, outputWidth/2, outputHeight/2);
    result.setLayerInset(1, ltrb[0], ltrb[1], ltrb[2], ltrb[3]);
    result.setBounds(0, 0, outputWidth, outputHeight);
    return result;
}

(Я тестировал Bitmap bm1 2400 x 1200 пикселей и Bitmap bm2 800 x 300 пикселей.)

Теперь, если я просто использую этот LayerDrawable, вот так

    myImageView.setImageDrawable(layd);

ImageView не будет иметь желаемого размера (высота изменится). Если я снова установлю макет с помощью LayoutParameters, я могу предотвратить это, но тогда чертежи отображаются неправильно. Если вместо этого я сделаю это

    LayerDrawable layd = makeLayerDrawable(r, outputWidth, outputHeight, bm1, bm2);
    Bitmap b = Bitmap.createBitmap(outputWidth, outputHeight, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(b);
    layd.draw(canvas);
    BitmapDrawable result = new BitmapDrawable(getResources(), b);
    result.setBounds(0, 0, outputWidth, outputHeight);
    myImageView.setImageDrawable(result);

оно работает.

Вот полный код на github


person mathheadinclouds    schedule 28.01.2015    source источник
comment
ImageView вызывает setBounds() для изображения, отображаемого как часть макета — вызов этого вручную для попытки установить границы не будет иметь никакого эффекта. Вам следует установить гравитацию на Слои BitmapDrawable вместо этого.   -  person alanv    schedule 28.01.2015
comment
@аланв. 1) Я думаю, что гравитация недостаточно гибка для моих целей. Один из рисунков не находится ни в центре, ни в каком-либо углу. 2) Я знаю, какими должны быть размеры представления в пикселях (screenWidth x screenWidth/3) - нет ли способа заставить ImageView вырезать попытки быть умнее меня и не вызывать setBounds()?   -  person mathheadinclouds    schedule 28.01.2015
comment
Вам нужно будет переопределить размер и макет ImageView по умолчанию. Ответ ниже, который предлагает написать свой собственный Drawable, вероятно, является лучшим вариантом, но вам может быть лучше создать подкласс LayerDrawable и переопределить метод onBoundsChange(), чтобы выполнять свои собственные вызовы setBounds() на отдельных слоях.   -  person alanv    schedule 28.01.2015


Ответы (1)


Я бы подумал о том, чтобы написать свой собственный класс Drawable, который не так сложен, как кажется. Просто расширяйтесь от Drawable и переопределяйте draw(), где вы делаете почти те же вычисления, что и в своем коде, отрезанном только для рисования в многослойные растровые изображения (сложенные) с требуемым соотношением сторон. Canvas.drawBitmap(), кажется, работает для вашей проблемы.

person sockeqwe    schedule 28.01.2015