Android 自定义图像视图形状

Posted

技术标签:

【中文标题】Android 自定义图像视图形状【英文标题】:Android custom image view shape 【发布时间】:2015-09-01 12:10:05 【问题描述】:

我正在创建一个自定义 ImageView,它将我的图像裁剪成六边形并添加边框。我想知道我的方法是正确的还是我做错了。有一堆自定义库已经做到了这一点,但没有一个开箱即用的库具有我正在寻找的形状。话虽如此,这更多是关于最佳实践的问题。

您可以在gist 中看到完整的课程,但主要问题是这是最好的方法。这对我来说感觉不对,部分原因是一些神奇的数字,这意味着它可能在某些设备上被搞砸了。

这是代码的核心:

@覆盖 受保护的无效onDraw(帆布画布) 可绘制可绘制 = getDrawable(); if (drawable == null || getWidth() == 0 || getHeight() == 0) 返回; 位图 b = ((BitmapDrawable) 可绘制).getBitmap(); 位图 bitmap = b.copy(Bitmap.Config.ARGB_8888, true); int dimensionPixelSize = getResources().getDimensionPixelSize(R.dimen.width); // (ImageView的宽高) 位图drawBitmap = drawCanvas(bitmap, dimensionPixelSize); canvas.drawBitmap(drawnBitmap, 0, 0, null); 私人位图drawCanvas(位图回收位图,int宽度) 最终位图位图 = verifyRecycledBitmap(recycledBitmap, width); 最终位图输出 = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888); 最终画布画布=新画布(输出); final Rect rect = new Rect(0, 0, width, width); final int offset = (int) (width / (double) 2 * Math.tan(30 * Math.PI / (double) 180)); // (宽度 / 2) * tan(30deg) 最终 int 长度 = 宽度 - (2 * 偏移量); 最终路径 path = new Path(); path.moveTo(width / 2, 0); // 顶部 path.lineTo(0, offset); // 左上角 path.lineTo(0, offset + length); // 左下角 path.lineTo(width / 2, width); // 底部 path.lineTo(宽度, 偏移 + 长度); // 右下角 path.lineTo(宽度,偏移); // 右上角 path.close(); //回到顶部 油漆油漆 = 新油漆(); 油漆.setStrokeWidth(4); canvas.drawARGB(0, 0, 0, 0); canvas.drawPath(路径,油漆); paint.setXfermode(新 PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(位图,矩形,矩形,油漆); // 绘制图像的位图 paint.setColor(Color.parseColor("white")); 油漆.setStrokeWidth(4); 油漆.setDither(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeCap(Paint.Cap.ROUND); paint.setPathEffect(新 CornerPathEffect(10)); paint.setAntiAlias(true); // 绘制边框 canvas.drawPath(路径,油漆); 返回输出;

我正在查看一些 ios 代码,他们能够应用实际图像作为蒙版来实现此结果。无论如何,android 上是否有这样的功能?

【问题讨论】:

也许this 适合您的需求? 我想说,让你的笔触宽度、偏移量 + X 和 CornerPathEffect 转换为 dp 的像素(使用 TypedValue),但除此之外,它看起来还不错。 30 度很好,因为 30 度在任何设备(或宇宙中的任何地方)上都是 30 度 @GilMoshayof 谢谢,我想我正在查看一些 iOS 代码,他们能够在他们的图像上应用 svg 作为图层。我希望我在 Android 中缺少一些东西可以让我做到这一点。 【参考方案1】:

长期以来,我一直在寻找最佳方法。您的解决方案非常繁重,不适用于动画。 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 使用这种方法在运行中为小部件圆角。

【讨论】:

在这里查看我的解决方案:github.com/tjohn/hexagon_image_view/blob/master/app/src/main/… 我有两个相互重叠的视图,那么如何将点击事件应用于它们?这是我的 SO 链接***.com/questions/36793535/…【参考方案2】:

尽管它可能有效,但在此实现中存在一些严重错误:

您在onDraw 阶段分配了一些非常大的对象,这导致了糟糕的性能。最重要的是createBitmap,但您应该在onDraw 期间不惜一切代价避免任何new。在初始化期间预分配所有必要的对象,并在 onDraw 期间重新使用它们。

您应该在onSizeChanged 期间只设置一次path。避开每个OnDraw

上的所有路径

您依赖于 BitmapDrawable 的使用,例如,如果您使用 Picasso 从 Internet 加载图像,或者如果您想使用选择器,那么此代码将不起作用。

您应该不需要分配第二个位图,而是使用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

【讨论】:

我同意,有性能改进,此代码不一定用于生产,而是更多地用于演示通用解决方案。就使用毕加索而言,这确实有效,这是我用来加载该图像的方法。我会调查clipPath 看看它是如何工作的。 我已经确认这是可行的,因为PicassoDrawable 扩展了BitamapDrawable 请参阅:github.com/square/picasso/blob/master/picasso/src/main/java/com/… 感谢有关性能的提示,但@zielony 对动画和硬件加速提出了很好的意见。【参考方案3】:

在 build.gradle 中添加依赖

   implementation 'com.github.siyamed:android-shape-imageview:0.9.+@aar'
   implementation group: 'net.sf.kxml', name: 'kxml2-min', version: '2.3.0'

xml

<?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_
android:layout_
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".MainActivity">

<com.github.siyamed.shapeimageview.HexagonImageView
    android:id="@+id/imageView"
    android:layout_marginTop="100dp"
    android:layout_
    android:layout_ />
    </LinearLayout>

活动

public class MainActivity extends AppCompatActivity 
HexagonImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    imageView=findViewById(R.id.imageView);
    imageView.setImageResource(R.drawable.img);

输出

【讨论】:

【参考方案4】:

在 build.gradle 中添加依赖

implementation 'com.github.siyamed:android-shape-imageview:0.9.+@aar'

xml

<?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_
    android:layout_
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".MainActivity">
    
    <com.github.siyamed.shapeimageview.mask.PorterShapeImageView
        android:id="@+id/imageView"
        android:layout_
        android:layout_
        app:siShape="@drawable/shape" />
    
</LinearLayout>

活动

public class MainActivity extends AppCompatActivity 

    ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageView=findViewById(R.id.imageView);
        imageView.setImageResource(R.drawable.img);
    

可绘制资源

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#FFC107" />
    <padding android:left="7dp"
        android:top="7dp"
        android:right="7dp"
        android:bottom="7dp" />
    <corners
        android:topLeftRadius="0dip"
        android:topRightRadius="70dip"
        android:bottomLeftRadius="70dip"
        android:bottomRightRadius="0dip" />
</shape>

输出

【讨论】:

以上是关于Android 自定义图像视图形状的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ios 中使用自定义形状创建 uiscrollview?

Android 自定义形状按钮

自定义图像裁剪器android

自定义形状的进度视图

带有波纹动画的Android自定义视图边缘裁剪

如何为形状添加自定义视图修改器