安卓画笔笔锋的实现探索(三)田字格Demo

Demo的下载地址

下载地址

效果图:如果对的效果不太明确的地方,请移步上两篇文章

效果图

Demo中做了哪些事情

1、提供画板,以及收起画板的动作
2、插入空格
3、换行
4、删除或者长按删除
5、切换笔的样式
6、根据手指抬起来自动插入已绘制的图形到EditText中
7、待续

1、提供画板,以及收起画板的动作

使用DrawViewLayout 继承FrameLayout

public class DrawViewLayout extends FrameLayout implements View.OnClickListener, View.OnLongClickListener {

    private RelativeLayout mShowKeyboard;
    private RelativeLayout mGotoPreviousStep;
    private RelativeLayout mClearCanvas;
    private NewDrawPenView mDrawView;
    private RelativeLayout mSaveBitmap;
    private ViewStub mViewStub;
    private View mChild;
    private Context mContext;
    private ImageView mUpOrDownIcon;
    private LayoutInflater mInflater;
    private int mPenConfig;
    private boolean mIsShowKeyB;

    public DrawViewLayout(@NonNull Context context) {
        this(context, null);
    }


    public DrawViewLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        initView();

    }
    private void initView() {
        mInflater = LayoutInflater.from(getContext());
        mChild = mInflater.inflate(R.layout.brush_weight_layout, this, false);
        addView(mChild);
        mShowKeyboard = (RelativeLayout) findViewById(R.id.rll_show_keyb_container);
        mGotoPreviousStep = (RelativeLayout) findViewById(R.id.rll_show_space_container);//空格
        mClearCanvas = (RelativeLayout) findViewById(R.id.rll_show_newline_container);
        mSaveBitmap = (RelativeLayout) findViewById(R.id.rll_show_delete_container);
        mViewStub = (ViewStub) findViewById(R.id.draw_view);
        //需要关心的selector的id
        mUpOrDownIcon = (ImageView) findViewById(R.id.rll_show_keyb_container_icon);
        setOnClickListenerT();
    }

布局文件使用的ViewStub轻量级的控件:使用Viewstub的在不需要弹出键盘的时候,渲染不占内存

    <ViewStub
        android:id="@+id/draw_view"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_280"
        android:layout_gravity="center"
        android:layout_marginLeft="@dimen/dp_40"
        android:layout_marginRight="@dimen/dp_40"
        android:layout_marginTop="@dimen/dp_10"
        android:layout_marginBottom="@dimen/dp_10"
        android:layout="@layout/draw_view_layout"
        android:visibility="visible"/>

判断是否填充过,没有的话就不填充,第一版设计的时候,产品经理说不用进入页面就弹出软件盘,那使用ViewStub,只要没有弹出来,渲染不占内存,后续产品经理说要弹出来,所以这里进入的时候,我都设计出弹出键盘

  if (mViewStub.getParent() != null) {
            mViewStub.inflate();
       }
//隐藏软键盘
  if (mDrawView.getVisibility() == GONE) {
            mIsShowKeyB=true;
            mViewStub.setVisibility(VISIBLE);
            mUpOrDownIcon.setSelected(true);
            Toast.makeText(mContext,"显示键盘",Toast.LENGTH_SHORT).show();
            mDrawView.setVisibility(VISIBLE);
        } else if (mDrawView.getVisibility() == VISIBLE) {
            mIsShowKeyB=false;
            Toast.makeText(mContext,"隐藏键盘",Toast.LENGTH_SHORT).show();
            mDrawView.setVisibility(GONE);
            mViewStub.setVisibility(GONE);
            mUpOrDownIcon.setSelected(false);
        }

2、插入空格

在这里说个开发中的趣事,当说要插入一个空格的时候,无非就是往EditText中插入一个空格吧,就用代码插入,但是后面ui说,你咋插入的空格那么小,我说本来就只有插入这样小啊,我能咋办,我也很绝望,后续使用的是插入一张空白的图

   @Override
    public void needSpace() {
        NewDrawPenView view = mDrawViewLayout.getSaveBitmap();
        if (view != null) {
            if (view.getHasDraw()) {
                mBitmap = view.getBitmap();
                mHandler.post(runnableUi);
                //保持一个联系
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mIsCreateBitmap = true;
                        if (mCreatBimap == null) {
                            mCreatBimap = creatBimap();
                        }
                        mHandler.post(runnableUi);
                    }
                }, 100);
            } else {
                mIsCreateBitmap = true;
                if (mCreatBimap == null) {
                    mCreatBimap = creatBimap();
                }
                mHandler.post(runnableUi);
            }
        }
        mHandler.removeCallbacks(runnable);
    }

创建一个空白的bitmap 这里其实不用创建一个和手机设备一样大的空白的bitmap,只创建一个和文本生成的字体一样大的就行了,总感觉浪费性能,哈哈

//创建一个bitmap 
    private Bitmap creatBimap() {
        ColorDrawable drawable = new ColorDrawable(Color.TRANSPARENT);
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        Bitmap bitmap = Bitmap.createBitmap(dm.widthPixels, dm.heightPixels, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.draw(canvas);
        return bitmap;
    }

把bitmap插入到edittext的线程

  Runnable runnableUi = new Runnable() {
        @Override
        public void run() {
            if (mIsCreateBitmap) {
                //110
                mBitmapResize = BitmapDrawUtils.resizeImage(mCreatBimap, mAllHandDrawSize, mAllHandDrawSize);
                mIsCreateBitmap = false;
            } else {
                mBitmapResize = BitmapDrawUtils.resizeImage(mBitmap, mAllHandDrawSize, mAllHandDrawSize);
            }
            if (mBitmapResize != null) {
                //根据Bitmap对象创建ImageSpan对象
                ImageSpan imageSpan = new ImageSpan(FieldCharacterShapeActivity.this, mBitmapResize);
                //创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像
                full_name = LAST_NAME + System.currentTimeMillis();
                String s =FONT_NAME_HEAD + full_name + FONT_NAME_TAIL;
                SpannableString spannableString = new SpannableString(s);
                //  用ImageSpan对象替换face
                spannableString.setSpan(imageSpan, 0, s.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                //将选择的图片追加到EditText中光标所在位置
                //                EditText ed = mSvContent.getFocusEditText();
                EditText ed = mRetContent.getLastFocusEdit();
                int index = ed.getSelectionStart(); //获取光标所在位置
                Editable edit_text = ed.getEditableText();
                if (index < 0 || index >= edit_text.length()) {
                    edit_text.append(spannableString);
                } else {
                    edit_text.insert(index, spannableString);
                }
                testStorage();
            }
            mDrawViewLayout.clearScreen();
        }
    };
   /**
     * 图片缩放
     * @param originalBitmap 原始的Bitmap
     * @param newWidth 自定义宽度
     * @return 缩放后的Bitmap
     */
    public static Bitmap resizeImage(Bitmap originalBitmap, int newWidth, int newHeight){
        if (originalBitmap==null||originalBitmap.getWidth()==0||originalBitmap.getHeight()==0){
            return null;
        }
        int width = originalBitmap.getWidth();
        int height = originalBitmap.getHeight();
        //定义欲转换成的宽、高
//            int newWidth = 200;
//            int newHeight = 200;
        //计算宽、高缩放率
        float scanleWidth = (float)newWidth/width;
        float scanleHeight = (float)newHeight/height;
        //创建操作图片用的matrix对象 Matrix
        Matrix matrix = new Matrix();
        matrix.postScale(scanleWidth,scanleHeight);
        // 创建新的图片Bitmap
        Bitmap resizedBitmap = Bitmap.createBitmap(originalBitmap,0,0,width,height,matrix,true);
        return resizedBitmap;
    }

3、换行

获取ScrollView最后的一个EditText,对了,这里没说,由于,需要滑动,所以自定义了一个ScrollView,可以插入很多控件,图片,文字和语音等等,好多的控件,由于这里不需要那么多,所以只保留了一个EditText

    /**
     * 这里是换行的需要
     */
    @Override
    public void creatNewLine() {
        EditText ed = mRetContent.getLastFocusEdit();
        int index = ed.getSelectionStart();
        Editable editable = ed.getText();
        editable.insert(index, "\n");
    }

4、删除或者长按删除

代码如下

    @Override
    public void deleteOnClick() {
        if (mRetContent.getLastFocusEdit().getSelectionStart() == 0) {
            mRetContent.onBackspacePress(mRetContent.getLastFocusEdit());
            if (mHandler!=null) {
                mHandler.removeCallbacks(runnable);
            }
        } else {
            SystemUtils.sendKeyCode(67);
        }
    }
 /**
     * <pre>
     * 使用Instrumentation接口:对于非自行编译的安卓系统,无法获取系统签名,只能在前台模拟按键,不能后台模拟
     * 注意:调用Instrumentation的sendKeyDownUpSync方法必须另起一个线程,否则无效
     * @param keyCode
     *            按键事件(KeyEvent)的按键值
     * </pre>
     */
    public static void sendKeyCode(final int keyCode) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 创建一个Instrumentation对象
                    Instrumentation inst = new Instrumentation();
                    // 调用inst对象的按键模拟方法
                    inst.sendKeyDownUpSync(keyCode);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

长按删除的事件呢,其实无非就是让这个方法不断的执行SystemUtils.sendKeyCode(67);在这里我使用枚举的单利模式,其实这样做不太好,因为枚举的单利比较消耗内存

package com.shiming.pen.field_character;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Message;

import com.shiming.pen.R;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


/**
 * @author shiming
 * @version v1.0 create at 2017/9/13
 * @des 使用模式为enum的单利模式
 */
public enum Executor {
    INSTANCE;
    private ScheduledExecutorService mScheduledExecutorService;
    private DrawViewLayout.IActionCallback mCallback;

    public void setCallback(DrawViewLayout.IActionCallback callback) {
        mCallback = callback;
    }

    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            int viewId = msg.what;
            switch (viewId) {
                case R.id.rll_show_delete_container:
                    if (mCallback == null)
                        return;
                    mCallback.deleteOnLongClick();
                    break;
            }
        }
    };

    public void upData(int id) {
        final int vid = id;
        //只有一个线程,用来调度执行将来的任务
        mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        //多少时间执行一次
        mScheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.what = vid;
                handler.sendMessage(msg);
            }
        }, 0, 100, TimeUnit.MILLISECONDS);    //每间隔100ms发送Message
    }

    public void stop() {
        if (mScheduledExecutorService != null) {
            mScheduledExecutorService.shutdownNow();
            mScheduledExecutorService = null;
        }
    }
}

执行的方法这里

  @Override
    public void deleteOnLongClick() {
        if (mRetContent.getLastFocusEdit().getSelectionStart() == 0) {
            mRetContent.onBackspacePress(mRetContent.getLastFocusEdit());
            if (mHandler!=null) {
                mHandler.removeCallbacks(runnable);
            }
        } else {
            SystemUtils.sendKeyCode(67);
        }
    }

5、切换笔的样式

这里笔的样式能有三种的情况,钢笔,水彩笔,和橡皮擦,由于我这里每次都自动的保存了图片,所以最后一次都是橡皮擦的模式,所以我只要判断不是Pen的模式,就给它赋予这种的模式,同时还需更具点击事件的变化而变化

  @Override
    public void onClick(View v) {
        int penConfig = mDrawViewLayout.getPenConfig();
        switch (v.getId()){
            case R.id.btn_change_pen:
                if (penConfig== IPenConfig.STROKE_TYPE_PEN){
                    penConfig=IPenConfig.STROKE_TYPE_BRUSH;
                }else {
                    penConfig=IPenConfig.STROKE_TYPE_PEN;
                }
                mDrawViewLayout.setPenConfig(penConfig);
                break;
        }
    }
   public void setPenConfig(int penConfig) {
       mDrawView.setCanvasCode(penConfig);
        mPenConfig=penConfig;
    }

最底层的方法 在这里 ,关键的地方需要invalidate一次,这样自定义view才知道需要变换

    public void setCanvasCode(int canvasCode) {
        mCanvasCode = canvasCode;
        switch (mCanvasCode) {
            case IPenConfig.STROKE_TYPE_PEN:
                mStokeBrushPen = new SteelPen(mContext);
                break;
            case IPenConfig.STROKE_TYPE_BRUSH:
                mStokeBrushPen = new BrushPen(mContext);
                break;

        }
        //设置
        if (mStokeBrushPen.isNull()){
            mStokeBrushPen.setPaint(mPaint);
        }
        invalidate();
    }

6、根据手指抬起来自动插入已绘制的图形到EditText中

在onTouchEvent事件中,记录手指抬起来的时间,同时记录下必须需要停止的时间

  @Override
    public boolean onTouchEvent(MotionEvent event) {
        mIsCanvasDraw = true;
        //测试过程中,当使用到event的时候,产生了没有收到事件的问题,所以在这里需要obtian的一下
        MotionEvent event2 = MotionEvent.obtain(event);
        switch (event2.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                //每次都是这个笔,因为项目里面就只有这个笔,如果多了,这里需要改动
                setCanvasCode(CANVAS_NORMAL);
                mVisualStrokePen.onDown(mVisualStrokePen.createMotionElement(event2));
                mGetTimeListner.stopTime();
                break;
            case MotionEvent.ACTION_MOVE:
                mVisualStrokePen.onMove(mVisualStrokePen.createMotionElement(event2));
                mGetTimeListner.stopTime();
                break;
            case MotionEvent.ACTION_UP:
                long time = System.currentTimeMillis();
                mGetTimeListner.getTime(time);
                mVisualStrokePen.onUp(mVisualStrokePen.createMotionElement(event2),mCanvas);
                break;
            default:
                break;
        }
        invalidate();
        return true;
    }

接口回调

        mDrawView.setGetTimeListener(new NewDrawPenView.TimeListener() {
            @Override
            public void getTime(long l) {
                mIActionCallback.getUptime(l);
            }

            @Override
            public void stopTime() {
                mIActionCallback.stopTime();
            }
        });

需要做什么,其实就是一个任务

    @Override
    public void getUptime(long l) {
        mOldTime = l;
        mHandler.postDelayed(runnable, 100);
    }

    @Override
    public void stopTime() {
        mHandler.removeCallbacks(runnable);
    }

当大于时间了,就使用handler发送一个消息

   final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            long l1 = System.currentTimeMillis();
            if ((l1 - mOldTime) > HADN_DRAW_TIME) {
                mHandler.removeCallbacks(runnable);
                Message msg = mHandler.obtainMessage();
                msg.obj = true;
                msg.what = 0x123;
                mHandler.sendMessage(msg);
            } else {
                mHandler.postDelayed(this, 100);
            }

        }
    };

处理handler消息,关键就是这个 mBitmap = view.clearBlank(100);

 @SuppressLint("HandlerLeak")//麻痹
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int what = msg.what;
            switch (what) {
                case 0x123:
                    try {
                        boolean obj = (boolean) msg.obj;
                        if (obj) {
                            NewDrawPenView view = mDrawViewLayout.getSaveBitmap();
                            if (view != null) {
                                //边距强行扫描
                                mBitmap = view.clearBlank(100);
                                mHandler.post(runnableUi);
                            }
                        }
                    } catch (Exception e) {

                    } finally {
                        mHandler.removeCallbacks(runnable);
                    }
                    break;
                case 0x124:
                    mRetContent.setVisibilityEdit(View.VISIBLE);
                    mRetContent.setVisibilityClose(View.VISIBLE);
                    mRetContent.getLastFocusEdit().setCursorVisible(true);
                    mRetContent.getLastFocusEdit().requestFocus();
                    break;
                case 0x125:
                    break;
            }
        }
    };

关于这个方法clearBlank(int ),这个方法不是我写的,我又一次研究别人的一个Demo,测试下了这个效果非常牛逼,本来这个方法打算废弃掉,但是最后觉得太可惜了,写这个田字格的Demo又提出来了。其实还有种方法可以实现,就是我们知道画布的大小,同时也知道,需要生成的小图的大小,我们可以动态的算,这就是另外一种方法,在不同设备上有点小许差异,但是也还可以

 /**
     * 逐行扫描 清楚边界空白。功能是生成一张bitmap位于正中间,不是位于顶部,此关键的是我们画布需要
     * 成透明色才能生效
     * @param blank 边距留多少个像素
     * @return tks github E-signature
     */
    public Bitmap clearBlank(int blank) {
        if (mBitmap != null) {
            int HEIGHT = mBitmap.getHeight();//1794
            int WIDTH = mBitmap.getWidth();//1080
            int top = 0, left = 0, right = 0, bottom = 0;
            int[] pixs = new int[WIDTH];
            boolean isStop;
            for (int y = 0; y < HEIGHT; y++) {
                mBitmap.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1);
                isStop = false;
                for (int pix : pixs) {
                    if (pix != mBackColor) {

                        top = y;
                        isStop = true;
                        break;
                    }
                }
                if (isStop) {
                    break;
                }
            }
            for (int y = HEIGHT - 1; y >= 0; y--) {
                mBitmap.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1);
                isStop = false;
                for (int pix : pixs) {
                    if (pix != mBackColor) {
                        bottom = y;
                        isStop = true;
                        break;
                    }
                }
                if (isStop) {
                    break;
                }
            }
            pixs = new int[HEIGHT];
            for (int x = 0; x < WIDTH; x++) {
                mBitmap.getPixels(pixs, 0, 1, x, 0, 1, HEIGHT);
                isStop = false;
                for (int pix : pixs) {
                    if (pix != mBackColor) {
                        left = x;
                        isStop = true;
                        break;
                    }
                }
                if (isStop) {
                    break;
                }
            }
            for (int x = WIDTH - 1; x > 0; x--) {
                mBitmap.getPixels(pixs, 0, 1, x, 0, 1, HEIGHT);
                isStop = false;
                for (int pix : pixs) {
                    if (pix != mBackColor) {
                        right = x;
                        isStop = true;
                        break;
                    }
                }
                if (isStop) {
                    break;
                }
            }
            if (blank < 0) {
                blank = 0;
            }
            left = left - blank > 0 ? left - blank : 0;
            top = top - blank > 0 ? top - blank : 0;
            right = right + blank > WIDTH - 1 ? WIDTH - 1 : right + blank;
            bottom = bottom + blank > HEIGHT - 1 ? HEIGHT - 1 : bottom + blank;
            return Bitmap.createBitmap(mBitmap, left, top, right - left, bottom - top);
        } else {
            return null;
        }
    }

最后,谢谢提出问题的大佬

GitHub:https://github.com/Shimingli/WritingPen

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,431评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 2017年新年签我抽到的是 温柔。 是啊,感觉自己越来越不够温柔了,内在,表象都缺少些柔和的东西。 女人的温柔是女...
    素颜hb阅读 311评论 0 0
  • 春天 你是我的棉被 温暖如春 感受到爱情的幸福 甜甜蜜蜜 夏天 你是我的风扇 神清气爽 体会到呵护的感觉 快快乐乐...
    我爱吃任何鱼阅读 214评论 0 3
  • 缘起 使用lambda代码会变得简短些,看起来舒服些。个人建议在不是很熟悉的情况下不使用这个,因为太爽会忘记原来这...
    明慢慢阅读 683评论 0 0