打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Android 放大镜效果实现原理

开源地址:https://github.com/jcodeing/ExtractWordView
效果图:


怎么实现的?
通过事件分发机制判断手指的落点处,剪取手指落点处的矩形区域,然后显示在PopupWindow里面.
第一步:自定义ListView

public class EWListView extends ListView
  • 1
  • 1

第二步:自定义一个Magnifier继承自view存放截图的屏幕图像(截取屏幕图像的时候应该做好边界判断控制),然后将这个view放入PopupWindow中悬浮展示。也就是那个放大镜。

    private void initMagnifier() {        BitmapDrawable resDrawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.ic_launcher1);        resBitmap = resDrawable.getBitmap();        magnifier = new Magnifier(context);        //pop在宽高的基础上多加出边框的宽高        popup = new PopupWindow(magnifier, WIDTH + 2, HEIGHT + 10);        popup.setAnimationStyle(android.R.style.Animation_Toast);        dstPoint = new Point(0, 0);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
    class Magnifier extends View {        private Paint mPaint;        public Magnifier(Context context) {            super(context);            mPaint = new Paint();            mPaint.setAntiAlias(true);            mPaint.setColor(0xffff0000);            mPaint.setStyle(Paint.Style.STROKE);        }        @Override        protected void onDraw(Canvas canvas) {            canvas.save();            // draw popup            mPaint.setAlpha(255);            canvas.drawBitmap(resBitmap, 0, 0, mPaint);            canvas.restore();            //draw popup frame            mPaint.reset();//重置            mPaint.setColor(Color.LTGRAY);            mPaint.setStyle(Paint.Style.STROKE);//设置空心            mPaint.setStrokeWidth(2);            Path path1 = new Path();            path1.moveTo(0, 0);            path1.lineTo(WIDTH, 0);            path1.lineTo(WIDTH, HEIGHT);            path1.lineTo(WIDTH / 2 + 15, HEIGHT);            path1.lineTo(WIDTH / 2, HEIGHT + 10);            path1.lineTo(WIDTH / 2 - 15, HEIGHT);            path1.lineTo(0, HEIGHT);            path1.close();//封闭            canvas.drawPath(path1, mPaint);        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

其中resBitmap就是截取的屏幕图像,通过划线的方式画出了一个倒三角的效果。
第三步:onTouchEvent(MotionEvent event) 在MotionEvent.ACTION_MOVE,通过手指点x,y坐标计算出popup显示的坐标,在手指点的正上方,每一次MotionEvent.ACTION_MOVE事件的到来都更新一次popup的位置。

popup.update(getLeft() + dstPoint.x, getTop() + dstPoint.y, -1, -1);
  • 1
  • 2
  • 1
  • 2

基本上按照上述步骤已经完成了,只需了解原理,死磕代码会浪费比较多的事件。
接下来看一下辅助功能,截取屏幕:

    /**     * @param activity     * @param x        截图起始的横坐标     * @param y        截图起始的纵坐标     * @param width     * @param height     * @return     */    private Bitmap getBitmap(Activity activity, int x, int y, int width, int height) {        View view = activity.getWindow().getDecorView();        view.setDrawingCacheEnabled(true);        view.buildDrawingCache();        Bitmap bitmap;//生成的位图        bitmap = view.getDrawingCache();        //边界处理,否则会崩滴        if (x < 0)            x = 0;        if (y < 0)            y = 0;        if (x + width > bitmap.getWidth()) {            //保持不改变,截取图片宽高的原则            x = bitmap.getWidth() - width;        }        if (y + height > bitmap.getHeight()) {            y = bitmap.getHeight() - height;        }        Rect frame = new Rect();        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);        bitmap = Bitmap.createBitmap(bitmap, x, y, width, height);        view.setDrawingCacheEnabled(false);        return bitmap;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

大致意思是获取Activity的顶级View也就是DecorView,是一个FrameLayout。拿到这个activity的Bitmap视图,bitmap = view.getDrawingCache();基于这个activity视图,截取以x,y为左上角顶点,宽度和高度分别为with,height的矩形,bitmap =Bitmap.createBitmap(bitmap, x, y, width, height);然后将得到的bitmap赋值给srcBitmap在Magnifier的onDraw里面重绘就行OK了。

获取文字:
1.先找到自定义的EditText

  EWListViewChildET findMotionView(int x, int y) {        //是否从顶部开始find提高效率        boolean isTopStart = y < getHeight() / 2;        int childCount = getChildCount();        if (childCount > 0) {            if (isTopStart) {                for (int i = 0; i < childCount; i++) {                    if (!(getChildAt(i) instanceof EWListViewChildET))                        return null;                    EWListViewChildET v = (EWListViewChildET) getChildAt(i);                    if (y <= v.getBottom()) {                        //特殊处理--更新EditText--相对自己的x,y                        v.y = y - v.getTop();                        v.x = x;                        Log.e("J", "ET-->y::" + y + "--updata->" + v.y);                        return v;                    }                }            } else {                for (int i = childCount - 1; i >= 0; i--) {                    if (!(getChildAt(i) instanceof EWListViewChildET))                        return null;                    EWListViewChildET v = (EWListViewChildET) getChildAt(i);                    if (y >= v.getTop()) {                        v.y = y - v.getTop();                        v.x = x;                        Log.e("J", "ET-->y::" + y + "--updata->" + v.y);                        return v;                    }                }            }        }        return null;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

类似于二分查找的方式,先通过isTopStart来判断是从上往下查找还是从下往上查找(提高查找效率),然后就是遍历每个item,看x,y有没有落到他的区域内,如果是就返回该自定义EditText.

2.得到按压的文字
1)首先得到按压位置的偏移,相对于本item本身,这个x,y是在findMotionView里面赋值的。

    public int extractWordCurOff(Layout layout, int x, int y) {        int line;        line = layout                .getLineForVertical(getScrollY() + y - 10);        int curOff = layout.getOffsetForHorizontal(line, x);        return curOff;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2)通过手指位置相对于item的偏移计算出当前按压的文字:

    public String getSelectWord(Editable content, int curOff) {        String word = "";        int start = getWordLeftIndex(content, curOff);        int end = getWordRightIndex(content, curOff);        if (start >= 0 && end >= 0) {            word = content.subSequence(start, end).toString();            if (!"".equals(word)) {                // setFocusable(false);                et.setFocusableInTouchMode(true);                et.requestFocus();                Selection.setSelection(content, start, end);// 设置当前具有焦点的文本字段的选择范围,当前文本必须具有焦点,否则此方法无效            }        }        return word;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3)计算出截取问题的其实位置
截取的开始位置索引:

 public int getWordLeftIndex(Editable content, int cur) {        // --left        String editableText = content.toString();// getText().toString();        if (cur >= editableText.length())            return cur;        int temp = 0;        //单词的长度一般不会超过20位,将temp索引往前推移20位        if (cur >= 20)            temp = cur - 20;        //正则表达式匹配单词开头        Pattern pattern = Pattern.compile("[^'A-Za-z]");        Matcher m = pattern.matcher(editableText.charAt(cur) + "");        //找到就返回索引        if (m.find())            return cur;        String text = editableText.subSequence(temp, cur).toString();        //没有找到就,从索引处往前面扫描,知道找到匹配的为止返回该处的索引        int i = text.length() - 1;        for (; i >= 0; i--) {            Matcher mm = pattern.matcher(text.charAt(i) + "");            if (mm.find())                break;        }        int start = i + 1;        start = cur - (text.length() - start);        return start;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

不清楚的地方可以看注释
截取结束位置索引:

    public int getWordRightIndex(Editable content, int cur) {        // --right        String editableText = content.toString();        if (cur >= editableText.length())            return cur;        int templ = editableText.length();        if (cur <= templ - 20)            templ = cur + 20;        Pattern pattern = Pattern.compile("[^'A-Za-z]");        Matcher m = pattern.matcher(editableText.charAt(cur) + "");        if (m.find())            return cur;        String text1 = editableText.subSequence(cur, templ).toString();        int i = 0;        for (; i < text1.length(); i++) {            Matcher mm = pattern.matcher(text1.charAt(i) + "");            if (mm.find())                break;        }        int end = i;        end = cur + end;        return end;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

原理跟寻找开始位置一样。

然后就可以根据开始位置和结束位置截取字符串了:

word = content.subSequence(start, end).toString()
  • 1
  • 1

OK,结束。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
【Android】缩略图Thumbnails
C# 捕获屏幕源码
简单实用!C#创建不规则窗体四种方式 - IT168 技术开发专区
8.3.1 三个绘图工具类详解
Android 画笔Paint
Android实现用户引导界面
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服