2015-06-16 2 views
1

Я работаю над созданием пользовательского ImageView, который обрезает изображение в форме шестиугольника и добавит границу. Мне было интересно, правильный ли мой подход или если я делаю это неправильно. Есть куча пользовательских библиотек, которые уже делают это, но ни один из них не имеет форму, которую я ищу. Это, как говорится, больше вопрос о лучшей практике.Форма пользовательского изображения для Android

expected result

Вы можете увидеть полный класс в этом gist, но главный вопрос заключается в том, что это лучший подход. Мне кажется, что это неправильно, отчасти из-за некоторых волшебных чисел, что означает, что он может быть испорчен на некоторых устройствах.

Вот мясо код:

 
     @Override 
     protected void onDraw(Canvas canvas) { 
     Drawable drawable = getDrawable(); 
     if (drawable == null || getWidth() == 0 || getHeight() == 0) { 
      return; 
     } 

     Bitmap b = ((BitmapDrawable) drawable).getBitmap(); 
     Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true); 

     int dimensionPixelSize = getResources().getDimensionPixelSize(R.dimen.width); // (width and height of ImageView) 
     Bitmap drawnBitmap = drawCanvas(bitmap, dimensionPixelSize); 
     canvas.drawBitmap(drawnBitmap, 0, 0, null); 
     } 

     private Bitmap drawCanvas(Bitmap recycledBitmap, int width) { 
     final Bitmap bitmap = verifyRecycledBitmap(recycledBitmap, width); 

     final Bitmap output = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888); 
     final Canvas canvas = new Canvas(output); 

     final Rect rect = new Rect(0, 0, width, width); 
     final int offset = (int) (width/(double) 2 * Math.tan(30 * Math.PI/(double) 180)); // (width/2) * tan(30deg) 
     final int length = width - (2 * offset); 

     final Path path = new Path(); 
     path.moveTo(width/2, 0); // top 
     path.lineTo(0, offset); // left top 
     path.lineTo(0, offset + length); // left bottom 
     path.lineTo(width/2, width); // bottom 
     path.lineTo(width, offset + length); // right bottom 
     path.lineTo(width, offset); // right top 
     path.close(); //back to top 

     Paint paint = new Paint(); 
     paint.setStrokeWidth(4); 
     canvas.drawARGB(0, 0, 0, 0); 
     canvas.drawPath(path, paint); 
     paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 
     canvas.drawBitmap(bitmap, rect, rect, paint); // draws the bitmap for the image 

     paint.setColor(Color.parseColor("white")); 
     paint.setStrokeWidth(4); 
     paint.setDither(true); 
     paint.setStyle(Paint.Style.STROKE); 
     paint.setStrokeJoin(Paint.Join.ROUND); 
     paint.setStrokeCap(Paint.Cap.ROUND); 
     paint.setPathEffect(new CornerPathEffect(10)); 
     paint.setAntiAlias(true); // draws the border 

     canvas.drawPath(path, paint); 

     return output; 
     } 

Я смотрел на какой-то IOS код и они могут применить реальное изображение в качестве маски, чтобы достичь этого результата. Есть ли в Android в любом случае сделать что-то подобное?

+0

Возможно, [это] (https://github.com/siyamed/android-shape-imageview) соответствует вашим потребностям? –

+0

Я бы сказал, сделайте свою ширину хода, смещение + X & CornerPathEffect конвертируйте в пиксели из dp (используя TypedValue), но кроме этого, он выглядит довольно хорошо.30 градусов - это хорошо, так как 30 градусов - 30 градусов на любом устройстве (или где-нибудь в юниверсе, если на то пошло) –

+0

@GilMoshayof спасибо, я думаю, я смотрел на какой-то код iOS, и они могут применять svg как слой поверх их изображения. Я надеялся, что в Android мне чего-то не хватает, что позволит мне это сделать. – trev9065

ответ

2

Я искал лучший подход в течение длительного времени. Ваше решение довольно тяжелое и не работает с анимацией. Подход clipPath не использует сглаживание и не работает с аппаратным ускорением в некоторых версиях Android (4.0 и 4.1?). Похоже, лучший подход (анимация дружелюбная, сглаженный, довольно чистая и аппаратное ускорение) является использование Canvas слоев:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 
private static PorterDuffXfermode pdMode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); 

@Override 
public void draw(Canvas canvas) { 
     int saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), 
             null, Canvas.ALL_SAVE_FLAG); 

     super.draw(canvas); 

     paint.setXfermode(pdMode); 
     canvas.drawBitmap(maskBitmap, 0, 0, paint); 

     canvas.restoreToCount(saveCount); 
     paint.setXfermode(null); 
} 

Вы можете использовать любой вид маски в том числе нестандартных форм и растровых изображений. Carbon использует такой подход для круглых углов виджетов на лету.

+0

Посмотрите мое решение здесь: https://github.com/tjohn/hexagon_image_view/blob/master/app/src/main/java/com/playdraft/hexagonimageview/HexagonImageView.java – trev9065

+0

Выглядит хорошо для меня :) – Zielony

+0

У меня есть два представления, которые перекрывают друг друга, поэтому как я могу применить событие click к обоим из них? Вот моя ссылка SO http://stackoverflow.com/questions/36793535/custom-shape-button-android –

0

Несмотря на то, что могло бы работать, там несколько плохих ошибок на этой реализации:

  • Вы Allocation несколько очень больших объектов во время onDraw фазы, что приводит к страшной производительности. Наиболее важным является createBitmap, но вы должны избегать любой ценой new во время onDraw. Предварительно распределите все необходимые объекты во время инициализации и повторите их использование в течение onDraw.

  • Вы должны установить path только один раз во время onSizeChanged. Избегайте все, что путь на каждый OnDraw

  • Вы опирающийся на использовании BitmapDrawable если, например, использовать Picasso для загрузки изображений из Интернета, или если вы хотите использовать селектор, то этот код не будет работать.

  • Вам не нужно выделять второй растровый рисунок, вместо этого используйте canvas.clipPath, чтобы сделать его эффективным.

сказал, что все, что гораздо более эффективным псевдокод для рисования должно быть:

@Override 
protected void onDraw(Canvas canvas) { 
    canvas.save(CLIP_SAVE_FLAG); // save the clipping 
    canvas.clipPath(path, Region.Op./*have to test which one*/); // cut the canvas 
    super.onDraw(canvas); // do the normal drawing 
    canvas.restore(); // restore the saved clipping 
    canvas.drawPath(path, paint); // draw the extra border 
} 
+0

Я согласен с тем, что есть улучшения производительности, этот код не обязательно предназначен для производства, но больше для демонстрации общего решения. Что касается использования picasso, это действительно работает, это то, что я использовал для загрузки этого изображения. Я посмотрю на «clipPath» и посмотрю, как это работает. – trev9065

+0

Я подтвердил, что это работает, потому что 'PicassoDrawable' расширяет' BitamapDrawable': https://github.com/square/picasso/blob/master/picasso/src/main/java/com/squareup/picasso/PicassoDrawable.java # L36 – trev9065

+0

Спасибо за советы о производительности, но @zielony дает хорошее представление об анимации и аппаратном ускорении. – trev9065

Смежные вопросы