#2020征文-手机# HarmonyOS App开发造轮子--自定义圆形图片组件 原创 精华

开源夏德旺
发布于 2021-1-5 01:36
浏览
9收藏

一、背景

        在采用Java配合xml布局编写鸿蒙app页面的时候,发现sdk自带的Image组件并不能将图片设置成圆形,反复了翻阅了官方API手册(主要查阅了Compont和Image相关的API),起初发现了一个setCornerRadius方法,于是想着将图片宽度和高度设置为一样,然后调用该方法将radios设置为宽度或者高度的一半,以为可以实现圆形图片的效果,后来发现不行。于是乎想着能不能通过继承原有的Image自己来动手重新自定义一个支持圆形的图片组件。

 

二、思路:

1、对比之前自己在其他程序开发中自定义组件的思路,首先寻找父组件Image和Component相关的Api,看看是否具备OnDraw方法。

2、了解Canvas相关Api操作,特别是涉及到位图的操作。

通过翻阅大量资料,发现了两个关键的api,分别是Component的addDrawTask方法和其内部静态接口DrawTask

#2020征文-手机# HarmonyOS App开发造轮子--自定义圆形图片组件-鸿蒙开发者社区

#2020征文-手机# HarmonyOS App开发造轮子--自定义圆形图片组件-鸿蒙开发者社区

三、自定义组件模块

1、新建一个工程之后,创建一个独立的Java FA模块,然后删除掉里面所有布局以及自动生成的java代码,然后自己创建一个class继承ImageView

2、写一个类继承ImageView,在其中暴露出public的设置圆形图片的api方法以供后面调用;

3、在原有的Image组件获取到位图之后,利用该位图数据利用addDrawTask方法配合Canvas进行位图输出形状的重新绘制,这里需要使用Canvas的一个

关键api方法drawPixelMapHolderRoundRectShape;

4、注意,为了让Canvas最后输出的图片为圆形,需要将图片在布局中的宽度和高度设置成一样,否则输出的为圆角矩形或者椭圆形。

 

最后封装后的详细代码如下:

package com.xdw.customview;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Image;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;

import java.io.InputStream;

/**
 * Created by 夏德旺 on 2021/1/1 11:00
 */
public class RoundImage extends Image {
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.DEBUG, 0, "RoundImage");
    private PixelMapHolder pixelMapHolder;//像素图片持有者
    private RectFloat rectDst;//目标区域
    private RectFloat rectSrc;//源区域
    public RoundImage(Context context) {
        this(context,null);

    }

    public RoundImage(Context context, AttrSet attrSet) {
        this(context,attrSet,null);
    }

    /**
     * 加载包含该控件的xml布局,会执行该构造函数
     * @param context
     * @param attrSet
     * @param styleName
     */
    public RoundImage(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        HiLog.error(LABEL,"RoundImage");
    }



    public void onRoundRectDraw(int radius){
        //添加绘制任务
        this.addDrawTask((view, canvas) -> {
            if (pixelMapHolder == null){
                return;
            }
            synchronized (pixelMapHolder) {
                //给目标区域赋值,宽度和高度取自xml配置文件中的属性
                rectDst = new RectFloat(0,0,getWidth(),getHeight());
                //绘制圆角图片
                canvas.drawPixelMapHolderRoundRectShape(pixelMapHolder, rectSrc, rectDst, radius, radius);
                pixelMapHolder = null;
            }
        });
    }

    //使用canvas绘制圆形
    private void onCircleDraw(){
        //添加绘制任务,自定义组件的核心api调用,该接口的参数为Component下的DrawTask接口
        this.addDrawTask((view, canvas) -> {
            if (pixelMapHolder == null){
                return;
            }
            synchronized (pixelMapHolder) {
                //给目标区域赋值,宽度和高度取自xml配置文件中的属性
                rectDst = new RectFloat(0,0,getWidth(),getHeight());
                //使用canvas绘制输出圆角矩形的位图,该方法第4个参数和第5个参数为radios参数,
                // 绘制图片,必须把图片的宽度和高度先设置成一样,然后把它们设置为图片宽度或者高度一半时则绘制的为圆形
                canvas.drawPixelMapHolderRoundRectShape(pixelMapHolder, rectSrc, rectDst, getWidth()/2, getHeight()/2);
                pixelMapHolder = null;
            }
        });
    }


    /**
     *获取原有Image中的位图资源后重新检验绘制该组件
     * @param pixelMap
     */
    private void putPixelMap(PixelMap pixelMap){
        if (pixelMap != null) {
            rectSrc = new RectFloat(0, 0, pixelMap.getImageInfo().size.width, pixelMap.getImageInfo().size.height);
            pixelMapHolder = new PixelMapHolder(pixelMap);
            invalidate();//重新检验该组件
        }else{
            pixelMapHolder = null;
            setPixelMap(null);
        }
    }


    /**
     * 通过资源ID获取位图对象
     **/
    private PixelMap getPixelMap(int resId) {
        InputStream drawableInputStream = null;
        try {
            drawableInputStream = getResourceManager().getResource(resId);
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            sourceOptions.formatHint = "image/png";
            ImageSource imageSource = ImageSource.create(drawableInputStream, null);
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            decodingOptions.desiredSize = new Size(0, 0);
            decodingOptions.desiredRegion = new Rect(0, 0, 0, 0);
            decodingOptions.desiredPixelFormat = PixelFormat.ARGB_8888;
            PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);
            return pixelMap;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try{
                if (drawableInputStream != null){
                    drawableInputStream.close();
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 对外调用的api,设置圆形图片方法
     * @param resId
     */
    public void setPixelMapAndCircle(int resId){
        PixelMap pixelMap = getPixelMap(resId);
        putPixelMap(pixelMap);
        onCircleDraw();
    }

    /**
     * 对外调用的api,设置圆角图片方法
     * @param resId
     * @param radius
     */
    public void setPixelMapAndRoundRect(int resId,int radius){
        PixelMap pixelMap = getPixelMap(resId);
        putPixelMap(pixelMap);
        onRoundRectDraw(radius);
    }
}

 

5、修改config.json文件,代码如下

{
  "app": {
    "bundleName": "com.xdw.customview",
    "vendor": "xdw",
    "version": {
      "code": 1,
      "name": "1.0"
    },
    "apiVersion": {
      "compatible": 4,
      "target": 4,
      "releaseType": "Beta1"
    }
  },
  "deviceConfig": {},
  "module": {
    "package": "com.xdw.customview",
    "deviceType": [
      "phone",
      "tv",
      "tablet",
      "car",
      "wearable"
    ],
    "reqPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ],
    "distro": {
      "deliveryWithInstall": true,
      "moduleName": "roundimage",
      "moduleType": "har"
    }
  }
}

这样该模块就可以导出后续给其他所有工程引用了,后面还可以编译之后发布到gradle上直接通过添加依赖来进行使用(这个是后话),下面我们先通过本地依赖导入的方式来调用这个自定义组件模块吧。

 

四、其他工程调用该自定义组件并测试效果

1、再来新建一个工程,然后将之前的模块导入到新建的工程中(DevEco暂时不支持自动导入外部模块的操作,需要手动导入操作,请关注我的另外一篇博客)

2、在gradle中引用导入的模块的组件,代码如下:

dependencies {
    entryImplementation project(':entry')
    implementation fileTree(dir: 'libs', include: ['*.jar', '*.har'])
    testCompile'junit:junit:4.12'
}

3、在布局中引用自定义的圆形图片,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical">

    <Text
        ohos:id="$+id:text_helloworld"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:background_element="$graphic:background_ability_main"
        ohos:layout_alignment="horizontal_center"
        ohos:text="Hello World"
        ohos:text_size="50"
        />

    <com.xdw.customview.RoundImage
        ohos:id="$+id:image"
        ohos:height="200vp"
        ohos:width="200vp"/>
</DirectionalLayout>

4、在Java代码中进行调用,代码如下:

package com.example.testcustomview.slice;

import com.example.testcustomview.ResourceTable;
import com.xdw.customview.RoundImage;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;

public class MainAbilitySlice extends AbilitySlice {
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);
        RoundImage roundImage = (RoundImage) findComponentById(ResourceTable.Id_image);
        roundImage.setPixelMapAndCircle(ResourceTable.Media_man);
    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}

5、开启手机模拟器进行测试,效果如下

#2020征文-手机# HarmonyOS App开发造轮子--自定义圆形图片组件-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
自定义圆形图片组件源代码.zip 1.39M 140次下载
已于2021-1-5 11:49:19修改
8
收藏 9
回复
举报
8条回复
按时间正序
/
按时间倒序
鸿蒙开发者社区官方账号
鸿蒙开发者社区官方账号

征文大赛正在火热进行中,楼主这么优秀的文章真的不考虑多写几篇文章投递到征文吗?

 

例如这篇在标题开头添加“#2020征文-其它“,
再找到相应的专栏位置投稿,


就可以参加比赛啦!

 

详细步骤可以点击链接https://harmonyos.51cto.com/posts/1940进行了解

用更多的文章来赢取更多的奖励和人气吧!期待楼主后续的活跃表现。

回复
2021-1-5 10:05:56
zk0301
zk0301

DevEco暂时不支持自动导入外部模块的操作,太那啥了

回复
2021-1-29 09:31:07
张荣超_九丘教育
张荣超_九丘教育

👍👍👍

回复
2021-2-16 20:24:52
开鸿包月东
开鸿包月东

自定义属性怎么弄?

回复
2021-3-1 14:33:42
开源夏德旺
开源夏德旺 回复了 开鸿包月东
自定义属性怎么弄?

专门针对你的问题写了篇博客,目前网上确实没有这块的博客,所以自己就此原创了一篇

https://harmonyos.51cto.com/posts/3334

回复
2021-3-4 15:54:39
AnBetter2021
AnBetter2021

学习了

回复
2021-4-26 08:58:47
软通田可辉
软通田可辉

这个帖子又上来了

回复
2021-4-26 09:12:56
大雁飞
大雁飞

赞,学习了

回复
2021-9-26 15:51:58
回复
    相关推荐