ScratchView:一步步打造万能的 Android 刮奖效果控件

Hello,大家好,我是Clock。这周为大家带来一篇关于自定义控件的文章,这也是我本人第一次写关于自定义控件的文章,希望可以写得言简意赅,通熟易懂点。

前言

我身边有一部分开发的小伙伴,存在着这样一种习惯。某一天,突然看到某一款 App 上有个很漂亮的自定义控件(动画)效果,就会绞尽脑子想办法去自己实现一发。当然,我自己也是属于这类型的骚年,看到某种效果就会手痒难耐琢磨着实现套路。个人觉得这是一种需求驱动进步的方法,当你绞尽脑子去实现自己想要的效果时,你就会发现你对 Android 自定义控件(动画)的知识体系认识越深,久而久之,自己也能轻松的造出各种控件(动画)效果。要是哪天,产品童鞋拿着个原型(或者对着某款 App )跟你讲:“XXXX,你看这个效果我们能不能实现?”,然后你瞥了一眼,胸有成竹丢回一句:“开玩笑,还有我实现不了的效果?”。想想心里是不是有点小激动?好了,差不多要说回正题了,这是我第一篇关于自定义控件的文章,以也会陆续穿插更新此类型的文章,希望大家能够喜欢。(偷偷剧透下,我下篇文章是关于性能优化的干货。当然我自己觉得很干货,希望到时候发出来不要打脸,哈哈哈!)

实现效果

说了这么多,还是先给大家看看最终的实现效果先。

上面只是基本实现效果的一部分,你会看到下方还有很多其他控件,它们是用来干嘛的,接下来即将为你揭晓一切。

基本实现

日常生活中,我们对刮奖效果想必不会陌生,其原理就是通过在原有图案和文字上添加刮层来实现的。如果我们想看到刮层后面藏的图案和文字是什么,势必要通过刮开刮层才行。知道了这样的套路,就可以开始整理一下编码实现思路,然后愉快开干。

我一开始的实现思路是想通过重写 ImageView 和 TextView ,然后在分别用代码在图像和文字上添加图层,这样的话就能实现出效果了。然而回头一想,不对,这种实现存在的局限性比较大。如果照这种思路实现,那么刮层下面只能存在图片或者文字,如果产品经理要求同时存在图片和文字呢?要求存在两张图片呢?要求同时存在图片和文字,且文字放在图片的上(下、左、右)呢?…我们都知道,世界上最善变的除了妹纸的心,就是产品经理和他们的需求了。于是,便想出另外一种实现思路,直接继承 View 来实现一个刮层,让这个刮层和图片以及文字不产生任何依赖,再结合 FrameLayout 将刮层放置最上一层,刮层之下你想放多少图片文字,图片文字要怎么布局摆放都行。到此,思路明确,可以愉快的开始编码了。

第一步:绘制出刮层效果。


package com.clock.scratch;

import ...;

/**
 * Created by Clock on 2016/8/26.
 */
public class ScratchView extends View {

    ...

    public ScratchView(Context context) {
        super(context);
        TypedArray typedArray = context.obtainStyledAttributes(R.styleable.ScratchView);
        init(typedArray);
    }

    ...

    private void init(TypedArray typedArray) {
        ...
        mMaskColor = typedArray.getColor(R.styleable.ScratchView_maskColor, DEFAULT_MASKER_COLOR);

        mMaskPaint = new Paint();
        mMaskPaint.setAntiAlias(true);//抗锯齿
        mMaskPaint.setDither(true);//防抖
        setMaskColor(mMaskColor);
        ...

    }

    /**
     * 设置蒙板颜色
     *
     * @param color 十六进制颜色值,如:0xffff0000(不透明的红色)
     */
    public void setMaskColor(int color) {
        mMaskPaint.setColor(color);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(mMaskBitmap, 0, 0, mBitmapPaint);//绘制图层遮罩
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        createMasker(w, h);
    }

    /**
     * 创建蒙层
     *
     * @param width
     * @param height
     */
    private void createMasker(int width, int height) {
        mMaskBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        mMaskCanvas = new Canvas(mMaskBitmap);
        Rect rect = new Rect(0, 0, width, height);
        mMaskCanvas.drawRect(rect, mMaskPaint);//绘制生成和控件大小一致的遮罩 Bitmap
    }

}

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ScratchView">
        <!--蒙层的颜色-->
        <attr name="maskColor" format="color|reference" />
    </declare-styleable>

</resources>

上面的代码思路如下:

  • 创建出继承于 View 的自定义控件 ScratchView,同时在init() 函数中初始化各类参数设置。如刮层的颜色等等;
  • 为了方便设置,需要把参数抽离成控件的自定义属性,同时 ScratchView 类中提供 set 方法,供代码调用。如刮层的颜色属性就是 maskColor ,其在类中对应的方法就是 setMaskColor;
  • 在 onSizeChanged 中,利用 View 已经 Measure 完毕,可以获得 View 的宽高,并使用 Canvas 来初始化生成 mMaskBitmap 用于制作刮层;
  • 在 onDraw 中,利用 canvas.drawBitmap 将 onSizeChanged 中初始化生成 mMaskBitmap 绘制显示到界面,生成刮层;

在 Demo 中添加如下布局,看下效果:


    <FrameLayout
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="8dp">
        <!--刮层下遮住的内容-->
        <ImageView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:layout_gravity="center"
            android:src="@mipmap/lufy" />
        <!--刮层-->
        <com.clock.scratch.ScratchView
            android:id="@+id/scratch_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>

到此,我们已经获得了一个刮层的实现效果,同时可以直接在 xml 布局和 java 代码中设置刮层的颜色了。但是这时候,只是空有刮层,并没有实现刮开的效果,接下来继续添加实现代码。

第二步:实现刮开效果。


package com.clock.scratch;

import ...;

public class ScratchView extends View {

    public ScratchView(Context context) {
        super(context);
        TypedArray typedArray = context.obtainStyledAttributes(R.styleable.ScratchView);
        init(typedArray);
    }

    private void init(TypedArray typedArray) {
        mEraseSize = typedArray.getFloat(R.styleable.ScratchView_eraseSize, DEFAULT_ERASER_SIZE);
        ...

        mErasePaint = new Paint();
        mErasePaint.setAntiAlias(true);
        mErasePaint.setDither(true);
        mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));//设置擦除效果
        mErasePaint.setStyle(Paint.Style.STROKE);
        mErasePaint.setStrokeCap(Paint.Cap.ROUND);//设置笔尖形状,让绘制的边缘圆滑
        setEraserSize(mEraseSize);

        mErasePath = new Path();

        ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
        mTouchSlop = viewConfiguration.getScaledTouchSlop();

    }

    /**
     * 设置橡皮檫尺寸大小(默认大小是 60)
     *
     * @param eraserSize 橡皮檫尺寸大小
     */
    public void setEraserSize(float eraserSize) {
        mErasePaint.setStrokeWidth(eraserSize);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                startErase(event.getX(), event.getY());
                invalidate();
                return true;
            case MotionEvent.ACTION_MOVE:
                erase(event.getX(), event.getY());
                invalidate();
                return true;
            case MotionEvent.ACTION_UP:
                stopErase();
                invalidate();
                return true;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 开始擦除
     *
     * @param x
     * @param y
     */
    private void startErase(float x, float y) {
        mErasePath.reset();
        mErasePath.moveTo(x, y);
        this.mStartX = x;
        this.mStartY = y;
    }

    /**
     * 擦除
     *
     * @param x
     * @param y
     */
    private void erase(float x, float y) {
        int dx = (int) Math.abs(x - mStartX);
        int dy = (int) Math.abs(y - mStartY);
        if (dx >= mTouchSlop || dy >= mTouchSlop) {
            this.mStartX = x;
            this.mStartY = y;

            mErasePath.lineTo(x, y);
            mMaskCanvas.drawPath(mErasePath, mErasePaint);

            mErasePath.reset();
            mErasePath.moveTo(mStartX, mStartY);
        }
    }


    /**
     * 停止擦除
     */
    private void stopErase() {
        this.mStartX = 0;
        this.mStartY = 0;
        mErasePath.reset();
    }

}

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ScratchView">
        <!--擦除尺寸大小-->
        <attr name="eraseSize" format="float" />
    </declare-styleable>

</resources>

上面的代码思路如下:

  • 在 init() 中初始化 mErasePaint 和 mErasePath ,并设置 mErasePaint 的 Xfermode 为 PorterDuff.Mode.CLEAR 用于后面制造出刮奖效果;
  • 重写 onTouchEvent 函数,处理触摸事件 ACTION_DOWN 、 ACTION_MOVE 、 ACTION_UP 等三种事件类型,并利用 mErasePath 记录手指滑动轨迹,再用 mMaskCanvas 将滑动轨迹绘制到第一步生成的 mMaskBitmap 上 ,最后通过调用 invalidate() 引起 View 的重绘生成刮开效果;
  • 为了防止滑动过于灵敏,我们需要对滑动做一个判断就是通过系统提供的 viewConfiguration.getScaledTouchSlop() 获取系统认为的最小滑动距离,当等于或者超过这个距离时,才认为是在滑动,这就是为什么我在 erase() 要加 dx >= mTouchSlop || dy >= mTouchSlop 的判断;
  • 为了控制刮痕的粗细,和前面设置刮层的颜色一样,同样为 ScratchView 自定义一个属性 eraseSize 实现在 xml 中控制。同时,在 Java 代码中提供调用方法;

到此,一个基本的刮奖效果已经完成了,我们来看看实现效果如何。

以上两步仅仅完成基础效果而已了,接下来我们来做一些优化。

效果优化

第一步优化:添加水印

很多刮奖的效果都会有在刮层上添加自家 logo 做水印效果(这里不知道称为水印合适吗?反正就是大概那个意思)。如下面的支付宝一样

我们在基础实现的第一步中的创建刮层函数里面添加实现代码,同时也添加一个自定义属性和 set 方法可供调用:


    /**
     * 设置水印图标
     *
     * @param resId 图标资源id,-1表示去除水印
     */
    public void setWatermark(int resId) {
        if (resId == -1) {
            mWatermark = null;
        } else {
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);
            mWatermark = new BitmapDrawable(bitmap);
            mWatermark.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
        }
    }

    /**
     * 创建蒙层
     *
     * @param width
     * @param height
     */
    private void createMasker(int width, int height) {
        ...

        if (mWatermark != null) {//
            Rect bounds = new Rect(rect);
            mWatermark.setBounds(bounds);
            mWatermark.draw(mMaskCanvas);
        }
    }

实现效果如下:

当然,像效果上还有很多可以进行添加,例如还可以加上面支付宝的那种边缘锯齿效果等等,这里就各位童鞋自行脑洞实现啦。

第二步优化:添加相应事件监听器,以及完善一些常用函数。

说到事件监听,我想这里莫过于刮奖完成的事件了吧。对于使用这个控件的开发者,肯定需要在刮完之后做相应的操作,例如,提示用户中奖啦,还是继续努力之类的。怎么样判断刮奖完成呢?这里的实现思路是通过异步计算刮层 mMaskBitmap 中的像素信息值,通过算得透明像素个数占总像素个数的比例,当这个比例超过一定阈值的时候,我们认为刮奖完成了。为什么要说超过一定阈值就算完成,这和现实生活中刮奖一样,你不需要把刮层完全刮得干干净净才能得到结果。当然这个比例是多少,我们同样需要抽离成可动态设置的。再添加监听器接口和设置监听器的 API 即可。实现代码,大致如下:


    private void onErase() {
        int width = getWidth();
        int height = getHeight();
        new AsyncTask<Integer, Integer, Boolean>() {

            @Override
            protected Boolean doInBackground(Integer... params) {
                int width = params[0];
                int height = params[1];
                int pixels[] = new int[width * height];
                mMaskBitmap.getPixels(pixels, 0, width, 0, 0, width, height);//获取覆盖图层中所有的像素信息,stride用于表示一行的像素个数有多少

                float erasePixelCount = 0;//擦除的像素个数
                float totalPixelCount = width * height;//总像素个数

                for (int pos = 0; pos < totalPixelCount; pos++) {
                    if (pixels[pos] == 0) {//透明的像素值为0
                        erasePixelCount++;
                    }
                }

                int percent = 0;
                if (erasePixelCount >= 0 && totalPixelCount > 0) {
                    percent = Math.round(erasePixelCount * 100 / totalPixelCount);
                    publishProgress(percent);
                }

                return percent >= mMaxPercent;
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);
                mPercent = values[0];
                onPercentUpdate();
            }

            @Override
            protected void onPostExecute(Boolean result) {
                super.onPostExecute(result);
                if (result && !mIsCompleted) {//标记擦除,并完成回调
                    mIsCompleted = true;
                    if (mEraseStatusListener != null) {
                        mEraseStatusListener.onCompleted(ScratchView.this);
                    }
                }
            }

        }.execute(width, height);
    }

    /**
     * 设置擦除监听器
     *
     * @param listener
     */
    public void setEraseStatusListener(EraseStatusListener listener) {
        this.mEraseStatusListener = listener;
    }

    /**
     * 擦除状态监听器
     */
    public static interface EraseStatusListener {

        /**
         * 擦除进度
         *
         * @param percent 进度值,大于0,小于等于100;
         */
        public void onProgress(int percent);

        /**
         * 擦除完成回调函数
         *
         * @param view
         */
        public void onCompleted(View view);
    }

我们来看看最终效果

到这里,一个完整的刮奖效果自定义控件实现已经完成。不过,这里还有一个问题需要抛给大家共同思考下,就是在判断刮奖是否完成的实现上,我在代码中的实现方式会创建出大量的 int 数组,这样造成后果就是会产生内存抖动。

目前,因为我本人也没想到什么好方案,所以,大家如果有好的思路,不妨在下方留言赐教一下。

总结

第一次写自定义控件这类型的文章,不知道大家看明白实现思路了吗?关于自定义控件,单看文章只能懂其中的思路,结合源代码边动手实践调试再加上文章会更深有体会。需要源代码的童鞋可以到 https://github.com/D-clock/ScratchView 中下载,接下来还有很多关于自定义控件(动画)的文章想写,敬请期待。

欢迎大家关注我的简书和Github

Scratch 之 TurboWarp 常用插件介绍-1 今天带来2篇 TurboWarp 常用插件介绍。什么你还没有 TurboWarp?快去下载一个吧。 阅读详情

相关推荐

Scratch 之 TurboWarp 常用插件介绍-2

今天带来2篇 TurboWarp 常用插件介绍什么你还没有 TurboWarp?快去下载一个吧。

leyang0910的博客 4390

Launcher布局加载流程

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Mar

yangshuangyue的博客 4762

android 状态栏背景最初透明,随着下滑恢复背景色

状态栏背景最初透明,随着下滑恢复背景色,主要应用于图片类app,参见blog.youkuaiyun.com/bisubisu/article/details/47835359

ScrimView的解析

ScrimView的解析

我的博客 444

AVL-Excite新手必看:5步搞定发动机阀系系统建模(附B站视频教程)

本文为AVL-Excite新手提供发动机阀系系统建模的5步详细指南,涵盖软件环境搭建、基本结构建立、关键参数设置、仿真分析及常见问题排查。通过清晰的步骤说明和实用技巧,帮助工程师快速掌握建模方法,并附有精选B站视频教程链接,便于实践学习。

weixin_29305313的博客 508

子图同构之乌尔曼算法Ullmann Algorithm的python实现

给定两个图Ga​(子图)和Gb​(原图),其中Ga​有pa​个点和qa​条边,Gb​有pb​个点和qb​条边,其邻接矩阵分别为A和B。子图同构的关键在于找到一种从Ga​的点到Gb​的点的映射MM是一个pa​行pb​列的矩阵,且满足每行只有一个1(表示Ga​中的一个点唯一地映射到Gb​中的一个点),每列至多一个1(确保Gb​中的点不会被重复映射)。定义Ccij​MMBT,若在图Ga​。

weixin_42822813的博客 1974

系统服务-SystemUI9.0(1)

SystemUI总结 https://www.jianshu.com/p/1a1c53cc44c3 android 下拉状态栏(SystemUI)常见修改记录 https://blog.youkuaiyun.com/Mis_wenwen/article/details/73468788 systemui整体介绍 https://cloud.tencent.com/developer/article/11553...

Gordon的博客 3570

kettle多表数据迁移

点击 文件——新建——作业,在左侧的 核心对象 标签下选择 通用,双击添加一个 start ,一个 转换 ,一个 作业 和一个 成功 到右侧的作业面板,这些作业项都可以改名字。点击文件—新建—转换,在左侧的 核心对象 标签下选择 输入 下的 表输入,双击添加到右侧的转换面板,再选择 作业 下的 复制记录到结果 ,双击添加到右侧的转换面板。新建转换,在左侧的 核心对象 标签下选择 作业 下的 从结果获取记录,双击添加到右侧的转换面板,再选择 作业 下的 设置变量 ,双击添加到右侧的转换面板。

u014061745的博客 1341

Launcher3源码分析之布局分析

Launcher3中会在Launcher.java中加载主布局launcher.xml <com.android.launcher3.LauncherRootView xmlns:android="<http://schemas.android.com/apk/res/android>" xmlns:launcher="<http://schemas.a...

一个小菜鸟的成长之路的专栏 1758

合宙ESP32C3实战:MPU6500六轴传感器数据读取与校准全解析

本文详细解析了如何使用合宙ESP32C3开发板读取和校准MPU6500六轴姿态传感器数据。从硬件接线、开发环境搭建到核心代码解析,提供了全面的实战指南,帮助开发者快速掌握传感器配置与数据处理技巧,适用于姿态控制、运动检测等应用场景。

weixin_27066963的博客 405

安卓刷Windows太难?这款手机平板双系统刷机工具,小白也能轻松拿捏!(小白福音!手机平板专用,一键搞定安卓刷 Windows 双系统!)

安卓刷Windows太难?这款手机平板双系统刷机工具,小白也能轻松拿捏!(小白福音!手机平板专用,一键搞定安卓刷 Windows 双系统!)

XiaoqiangClub的博客 4672

ScratchView:一步步打造万能的 Android 刮奖效果控件

前言 我身边有一部分开发的小伙伴,存在着这样一种习惯。某一天,突然看到某一款 App 上有个很漂亮的自定义控件(动画)效果,就会绞尽脑子想办法去自己实现一发。当然,我自己也是属于这类型的骚年,看到某种效果就会手痒难耐琢磨着实现套路。个人觉得这是一种需求驱动进步的方法,当你绞尽脑子去实现自己想要的效果时,你就会发现你对 Android 自定义控件(动画)的知识体系认识越深,久而久之,自己也

shenggaofei的博客 1480

python点云可视化

Python三种点云可视化方案:mayavi、matplotlib、CloudCompare。

叶子的博客 1万+

自定义控件 ScratchView:一步步打造万能的 Android 刮奖效果控件

Hello,大家好,我是Clock。这周为大家带来一篇关于自定义控件的文章,这也是我本人第一次写关于自定义控件的文章,希望可以写得言简意赅,通熟易懂点。 前言 我身边有一部分开发的小伙伴,存在着这样一种习惯。某一天,突然看到某一款 App 上有个很漂亮的自定义控件(动画)效果,就会绞尽脑子想办法去自己实现一发。当然,我自己也是属于这类型的骚年,看到某种效果就会手痒难耐琢磨着实现套路。个

hery186的专栏 1093

雷达方程从零开始的详细推导及实现

通过以上六步,我们从最基本的物理图像出发,完整地推导出了雷达方程。初学者可以按照这个逻辑,自己在纸上一步步重现整个过程。Python实现。

VectorShift的博客 336

2023年12月青少年C/C++软件编程(三级)等级考试试卷及答案解析

有一批易感人群住在网格状的宿舍区内,宿舍区为n*n的矩阵,每个格点为一个房间,房间里可能住人,也可能空着。在第一天,有些房间里的人得了流感,以后每天,得流感的人会使其邻居传染上流感,(已经得病的不变),空房间不会传染。第一行一个数字n,n不超过100,表示有n*n的宿舍房间。接下来的n行,每行n个字符,’.’表示第一天该房间住着健康的人,’#’表示该房间空着,’@’表示第一天该房间住着得流感的人。输出为一行,表达式的值。任给两个正整数N、M,求一个最小的正整数a,使得a和(M-a)都是N的因子。

长春高老师编程 1511

刮奖效果控件--ScratchView

最近看了一篇公众号推送的文章,关于Android刮奖效果的自定义控件,感觉蛮有意思,所以就模仿他也写了一个自定义ScratchView,人家的文笔不错,所以建议可以先看他的文章:ScratchView:一步步打造万能的 Android 刮奖效果控件;然后再来阅读我的,那为什么还要看我的呢?因为我针对内存优化和监听时机两个方面做了改进。     考虑如果你没看刚才那位的文章,那后面说的优化可能

海牛宝宝的蟹堡王 1190

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

java画板_java 画板画图程序

导读热词下面是编程之家 jb51.cc 通过网络收集整理的代码片段。编程之家小编现在分享给大家,也给大家做个参考。import java.awt.*;import javax.swing.*;import java.awt.event.*;public class Board {/*** @param args*/public static void main(String[] args) {//...

weixin_28737399的博客 348

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

HTML5模仿刮奖效果-页面涂抹消失插件wScratch

本文转至:http://www.xuanfengge.com/html5-imitates-scratch-effect-disappear-page-smear-plug-in-wscratch.html 前言 HTML5的canvas有很多应用点,如绘画板、图形绘制、游戏交互、彩票刮刮乐等,除了这些,还有个比较好的点就是主页涂抹一部分之后,页面消失进入主要内容。 wScrat

yzbben的博客 2142

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

【亲测免费】 Scratch Addons:为Scratch注入无限可能

**Scratch Addons** 是一个专为 [Scratch](https://scratch.mit.edu) 网站和项目编辑器设计的浏览器扩展。它集成了新旧功能和主题,为用户提供了一个集中且可配置的平台。Scratch Addons 的使命是为社区开发新功能和主题提供一个最新、最便捷的工具,从而增强 Scratch 的使用体验。 通过 Scratch Addons,用户可以轻松访问和配

gitblog_00333的博客 774

scratch 自定义插件 之 串口控制

前面讲了一个Scratch进行自定义开发的语法(链接),本文做个简单的串口控制Demo,使得通过Scratch可以通过串口下发数据来控制实际物理设备(通过USB转TTL或其它协议实现串口通信)。 本文要实现的最终目的,是通过在线Scratch控制本地设备执行不同操作,本地设备为一个简单的三轴小机械臂,实现的功能有抬起、放下、向前、向后、抓住、放开(具体功能只要跟设备协议对接好即可,这里只是做个示...

具身小站 7225

TLPhotoPicker扩展开发:自定义数据源与委托模式完全指南

TLPhotoPicker是一款强大的iOS图片选择框架,支持多选功能,界面风格类似Facebook。本文将详细介绍如何通过自定义数据源和委托模式扩展TLPhotoPicker的功能,帮助开发者打造个性化的图片选择体验。 ## 为什么需要自定义数据源? TLPhotoPicker默认提供了完整的图片选择功能,但在实际开发中,我们常常需要根据项目需求定制界面展示方式。例如: - 修改相册分组的头

gitblog_00998的博客 526

scratch 开发自定义插件

自定义插件需要两步: 登录ScratchX平台 加载自定义程序 第一步相对固定,打开网站地址为:http://scratchx.org/#scratch,选择“Load Experimental Extension”按钮,在右上角或者“More Blocks”模块下,如图 第二步是在弹窗中输入自己的脚本程序,程序语法按照Scratch的要求实现即可,包含三部分,其格式是固定的,框架如下:...

具身小站 1万+
上一篇: 知乎和简书的夜间模式实现套路
下一篇: 深入了解 Messenger 的实现细节
D_clock爱吃葱花
博客等级 码龄14年 163粉丝 13原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值