├── .gitignore
├── ExtractWordView-demo.apk
├── LICENSE
├── README.md
├── build.gradle
├── lookme.gif
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── jcodeing
│ └── extractwordview
│ ├── MainActivity.java
│ └── widget
│ ├── EWListView.java
│ └── EWListViewChildET.java
└── res
├── layout
├── activity_main.xml
└── ewlv_item.xml
├── menu
└── menu_main.xml
├── mipmap-xxhdpi
└── ic_launcher.png
└── values
├── strings.xml
└── styles.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | # *.apk #Demo
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 | /*/build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | *.iml
--------------------------------------------------------------------------------
/ExtractWordView-demo.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jcodeing/ExtractWordView/877b544c298d29a1c73b7b840a44438b9ce70973/ExtractWordView-demo.apk
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jcodeing
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ExtractWordView #
2 | Extract Word Demo For Android
3 |
4 | 
5 |
6 | Introduction
7 | ============
8 | 这是一个,可以提取 英文单词 的 ListView Demo
9 | 长按文章时,启动题词功能,含有 ”放大镜“ 功能,打开了手指触摸下的视角。
10 | 长按,后可随意滑动,滑到那里,取到那里。松开后,便可获取,以选择的单词。
11 | Features
12 | ========
13 | 在android原生控件ListView上实现滑动取词。
14 | 可以随意滑动取词,跨item进行取词。取词代码在ListView的onTouchEvent中处理。
15 | Usage
16 | =====
17 | 只需将widget下的
18 | EWListView.java
19 | EWListViewChildET.java
20 | 两个文件copy到你的工程中,
21 | ListView用EWListView,或者,直接复制里面的逻辑到你自定义的ListView中即可
22 | Item用EWListViewChildET,或者copy,逻辑到你的item中。
23 | License
24 | =======
25 | The MIT License (MIT)
26 |
27 | Copyright (c) 2015 Jcodeing
28 |
29 | Permission is hereby granted, free of charge, to any person obtaining a copy
30 | of this software and associated documentation files (the "Software"), to deal
31 | in the Software without restriction, including without limitation the rights
32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
33 | copies of the Software, and to permit persons to whom the Software is
34 | furnished to do so, subject to the following conditions:
35 |
36 | The above copyright notice and this permission notice shall be included in all
37 | copies or substantial portions of the Software.
38 |
39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45 | SOFTWARE.
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.2"
6 |
7 | defaultConfig {
8 | applicationId "com.jcodeing.extractwordview"
9 | minSdkVersion 9
10 | targetSdkVersion 21
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:21.0.3'
25 | }
26 |
--------------------------------------------------------------------------------
/lookme.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jcodeing/ExtractWordView/877b544c298d29a1c73b7b840a44438b9ce70973/lookme.gif
--------------------------------------------------------------------------------
/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Applications/Office/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/java/com/jcodeing/extractwordview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.jcodeing.extractwordview;
2 |
3 | import android.content.res.Resources;
4 | import android.os.Bundle;
5 | import android.support.v7.app.ActionBarActivity;
6 | import android.view.LayoutInflater;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.BaseAdapter;
12 |
13 | import com.jcodeing.extractwordview.widget.EWListView;
14 | import com.jcodeing.extractwordview.widget.EWListViewChildET;
15 |
16 |
17 | public class MainActivity extends ActionBarActivity {
18 | private EWListView ewlv;
19 | private String[] poetry;
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_main);
25 | //init data
26 | Resources res = getResources();
27 | poetry = res.getStringArray(R.array.poetry);
28 | //init view
29 | ewlv = (EWListView) findViewById(R.id.ewlv);
30 | //
31 | ewlv.activity=this;
32 | ewlv.setAdapter(new EwlvAdapter());
33 | }
34 |
35 | class EwlvAdapter extends BaseAdapter {
36 |
37 | @Override
38 | public int getCount() {
39 | return poetry.length;
40 | }
41 |
42 | @Override
43 | public Object getItem(int position) {
44 | return null;
45 | }
46 |
47 | @Override
48 | public long getItemId(int position) {
49 | return 0;
50 | }
51 |
52 | @Override
53 | public View getView(int position, View convertView, ViewGroup parent) {
54 | String verse = poetry[position];
55 | ViewHolder holder;
56 | if (convertView == null) {
57 | convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.ewlv_item, null);
58 | holder = new ViewHolder();
59 | holder.ewe_text = (EWListViewChildET)
60 | convertView;
61 | convertView.setTag(holder);
62 | } else {
63 | holder = (ViewHolder) convertView.getTag();
64 | }
65 | holder.ewe_text.setText(verse);
66 | return convertView;
67 | }
68 | }
69 |
70 | class ViewHolder {
71 | EWListViewChildET ewe_text;
72 | }
73 |
74 |
75 | @Override
76 | public boolean onCreateOptionsMenu(Menu menu) {
77 | // Inflate the menu; this adds items to the action bar if it is present.
78 | getMenuInflater().inflate(R.menu.menu_main, menu);
79 | return true;
80 | }
81 |
82 | @Override
83 | public boolean onOptionsItemSelected(MenuItem item) {
84 | // Handle action bar item clicks here. The action bar will
85 | // automatically handle clicks on the Home/Up button, so long
86 | // as you specify a parent activity in AndroidManifest.xml.
87 | int id = item.getItemId();
88 |
89 | //noinspection SimplifiableIfStatement
90 | if (id == R.id.action_settings) {
91 | return true;
92 | }
93 |
94 | return super.onOptionsItemSelected(item);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/com/jcodeing/extractwordview/widget/EWListView.java:
--------------------------------------------------------------------------------
1 | package com.jcodeing.extractwordview.widget;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.Path;
10 | import android.graphics.Point;
11 | import android.graphics.Rect;
12 | import android.graphics.drawable.BitmapDrawable;
13 | import android.os.Bundle;
14 | import android.os.Handler;
15 | import android.os.Message;
16 | import android.text.Editable;
17 | import android.text.Layout;
18 | import android.text.Selection;
19 | import android.text.TextUtils;
20 | import android.util.AttributeSet;
21 | import android.util.Log;
22 | import android.view.Gravity;
23 | import android.view.MotionEvent;
24 | import android.view.View;
25 | import android.widget.ListView;
26 | import android.widget.PopupWindow;
27 | import android.widget.Toast;
28 |
29 | import com.jcodeing.extractwordview.R;
30 |
31 | import java.util.regex.Matcher;
32 | import java.util.regex.Pattern;
33 |
34 | /**
35 | * The MIT License (MIT)
36 | *
37 | * Copyright (c) 2015 Jcodeing
38 | *
39 | * Permission is hereby granted, free of charge, to any person obtaining a copy
40 | * of this software and associated documentation files (the "Software"), to deal
41 | * in the Software without restriction, including without limitation the rights
42 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
43 | * copies of the Software, and to permit persons to whom the Software is
44 | * furnished to do so, subject to the following conditions:
45 | *
46 | * The above copyright notice and this permission notice shall be included in all
47 | * copies or substantial portions of the Software.
48 | *
49 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
50 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
51 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
52 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
53 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
54 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
55 | * SOFTWARE.
56 | */
57 | public class EWListView extends ListView {
58 | public Activity activity;
59 | public boolean isSupportExtractWord = true;
60 | private boolean isLongPressState;
61 | Context context;
62 |
63 | // ---------三个构造----------------------------------------------$构造
64 | // 当设置,指定样式时调用
65 | public EWListView(Context context, AttributeSet attrs, int defStyle) {
66 | super(context, attrs, defStyle);
67 | initialize(context);
68 | }
69 |
70 | // 布局文件初始化的时候,调用-------该构造方法,重用------------★
71 | // 布局文件里面定义的属性都放在 AttributeSet attrs
72 | public EWListView(Context context, AttributeSet attrs) {
73 | super(context, attrs);
74 | initialize(context);
75 | }
76 |
77 | // 该方法,一般,在代码中 new 该类的时候_使用
78 | public EWListView(Context context) {
79 | super(context);
80 | initialize(context);
81 | }
82 |
83 | // --------------------------------------------------------------$初始
84 | private void initialize(Context context) {
85 | this.context = context;
86 | initMagnifier();
87 | }
88 |
89 |
90 | private final int LONGPRESS = 1;
91 | private Handler mPressHandler = new Handler() {
92 | public void handleMessage(Message msg) {
93 | switch (msg.what) {
94 | //长按->初次启动--->显示放大镜&提词
95 | case LONGPRESS:
96 | isLongPressState = true;
97 | Bundle data = msg.getData();
98 | int X = data.getInt("X");
99 | int RawX = data.getInt("RawX");
100 | int Y = data.getInt("Y");
101 | int RawY = data.getInt("RawY");
102 | if (!isMoved) {
103 | et = findMotionView(X, Y);
104 | word = getSelectWord(et.getEditableText(), extractWordCurOff(et.getLayout(), et.x, et.y));
105 | }
106 | resBitmap = getBitmap(activity, RawX - WIDTH / 2, RawY - HEIGHT / 2, WIDTH, HEIGHT);
107 | //放大镜-初次显示
108 | calculate(RawX, RawY, MotionEvent.ACTION_DOWN);
109 | break;
110 | }
111 | }
112 |
113 | };
114 |
115 |
116 | private int mLastMotionX,
117 | mLastMotionY;
118 | // 是否移动了
119 | private boolean isMoved;
120 | // 移动的阈值
121 | private static final int TOUCH_SLOP = 20;
122 |
123 | private EWListViewChildET et;
124 | private String word;
125 |
126 | @Override
127 | public boolean onTouchEvent(MotionEvent event) {
128 | if (!isSupportExtractWord)
129 | return super.onTouchEvent(event);
130 | int x = (int) event.getX();
131 | int y = (int) event.getY();
132 | switch (event.getAction()) {
133 | case MotionEvent.ACTION_DOWN:
134 | mLastMotionX = x;
135 | mLastMotionY = y;
136 | isMoved = false;
137 |
138 | Message message = mPressHandler != null ? mPressHandler.obtainMessage()
139 | : new Message();
140 | //传对象,过去后,getRawY,不是相对的Y轴.
141 | // message.obj = event;
142 | Bundle bundle = new Bundle();
143 | bundle.putInt("X", (int) event.getX());
144 | bundle.putInt("RawX", (int) event.getRawX());
145 | bundle.putInt("Y", (int) event.getY());
146 | bundle.putInt("RawY", (int) event.getRawY());
147 | message.setData(bundle);
148 | message.what = LONGPRESS;
149 | mPressHandler.sendMessageDelayed(message, 500);
150 | break;
151 | case MotionEvent.ACTION_MOVE:
152 | if (isLongPressState)
153 | if (Math.abs(mLastMotionX - x) > TOUCH_SLOP
154 | || Math.abs(mLastMotionY - y) > TOUCH_SLOP) {
155 | //提词
156 | et = findMotionView(x, y);
157 | Log.e("J", et.x + "-ET--move--ET-" + et.y + "cont:" + et.getEditableText().toString().charAt(0));
158 | word = getSelectWord(et.getEditableText(), extractWordCurOff(et.getLayout(), et.x, et.y));
159 | //放大镜
160 | resBitmap = getBitmap(activity, (int) event.getRawX() - WIDTH / 2, (int) event.getRawY() - HEIGHT / 2, WIDTH, HEIGHT);
161 | calculate((int) event.getRawX(), (int) event.getRawY(), MotionEvent.ACTION_MOVE);
162 | return true;
163 | }
164 | if (isMoved && !isLongPressState)
165 | break;
166 | //如果移动超过阈值
167 | if (Math.abs(mLastMotionX - x) > TOUCH_SLOP
168 | || Math.abs(mLastMotionY - y) > TOUCH_SLOP)
169 | //并且非长按状态下
170 | if (!isLongPressState) {
171 | // 则表示移动了
172 | isMoved = true;
173 | cleanLongPress();// 如果超出规定的移动范围--取消[长按事件]
174 |
175 | }
176 | break;
177 | case MotionEvent.ACTION_UP:
178 | if (isLongPressState) {
179 | //dis掉放大镜
180 | removeCallbacks(showZoom);
181 | //drawLayout();
182 | popup.dismiss();
183 |
184 | //TODO --单词pop
185 | cleanLongPress();
186 |
187 | if (!TextUtils.isEmpty(word) && et != null)
188 | onLongPressWord(word, et);
189 | break;
190 | }
191 | cleanLongPress();// 只要一抬起就释放[长按事件]
192 | break;
193 | case MotionEvent.ACTION_CANCEL:
194 | // 事件一取消也释放[长按事件],解决在ListView中滑动的时候长按事件的激活
195 | cleanLongPress();
196 | break;
197 | }
198 | return super.onTouchEvent(event);
199 | }
200 |
201 |
202 | private void cleanLongPress() {
203 | isLongPressState = false;
204 | mPressHandler.removeMessages(LONGPRESS);
205 | }
206 |
207 | private boolean calculate(int x, int y, int action) {
208 | dstPoint.set(x - WIDTH / 2, y - 3 * HEIGHT);
209 | if (y < 0) {
210 | // hide popup if out of bounds
211 | popup.dismiss();
212 | return true;
213 | }
214 | if (action == MotionEvent.ACTION_DOWN) {
215 | removeCallbacks(showZoom);
216 | postDelayed(showZoom, DELAY_TIME);
217 | } else if (!popup.isShowing()) {
218 | showZoom.run();
219 | }
220 | popup.update(getLeft() + dstPoint.x, getTop() + dstPoint.y, -1, -1);
221 | magnifier.invalidate();
222 | return true;
223 | }
224 | // --------------------------------------------------------------$方法
225 |
226 | // 单词提取
227 |
228 | /**
229 | * @param layout
230 | * @param x 相对自己ev.getX()
231 | * @param y
232 | * @return
233 | */
234 | public int extractWordCurOff(Layout layout, int x, int y) {
235 | int line;
236 | line = layout
237 | .getLineForVertical(getScrollY() + y - 10);
238 | int curOff = layout.getOffsetForHorizontal(line, x);
239 | return curOff;
240 | }
241 |
242 | public String getSelectWord(Editable content, int curOff) {
243 | String word = "";
244 | int start = getWordLeftIndex(content, curOff);
245 | int end = getWordRightIndex(content, curOff);
246 | if (start >= 0 && end >= 0) {
247 | word = content.subSequence(start, end).toString();
248 | if (!"".equals(word)) {
249 | // setFocusable(false);
250 | et.setFocusableInTouchMode(true);
251 | et.requestFocus();
252 | Selection.setSelection(content, start, end);// 设置当前具有焦点的文本字段的选择范围,当前文本必须具有焦点,否则此方法无效
253 | }
254 | }
255 | return word;
256 | }
257 |
258 | public int getWordLeftIndex(Editable content, int cur) {
259 | // --left
260 | String editableText = content.toString();// getText().toString();
261 | if (cur >= editableText.length())
262 | return cur;
263 |
264 | int temp = 0;
265 | if (cur >= 20)
266 | temp = cur - 20;
267 | Pattern pattern = Pattern.compile("[^'A-Za-z]");
268 | Matcher m = pattern.matcher(editableText.charAt(cur) + "");
269 | if (m.find())
270 | return cur;
271 |
272 | String text = editableText.subSequence(temp, cur).toString();
273 | int i = text.length() - 1;
274 | for (; i >= 0; i--) {
275 | Matcher mm = pattern.matcher(text.charAt(i) + "");
276 | if (mm.find())
277 | break;
278 | }
279 | int start = i + 1;
280 | start = cur - (text.length() - start);
281 | return start;
282 | }
283 |
284 | public int getWordRightIndex(Editable content, int cur) {
285 | // --right
286 | String editableText = content.toString();
287 | if (cur >= editableText.length())
288 | return cur;
289 |
290 | int templ = editableText.length();
291 | if (cur <= templ - 20)
292 | templ = cur + 20;
293 | Pattern pattern = Pattern.compile("[^'A-Za-z]");
294 | Matcher m = pattern.matcher(editableText.charAt(cur) + "");
295 | if (m.find())
296 | return cur;
297 |
298 | String text1 = editableText.subSequence(cur, templ).toString();
299 | int i = 0;
300 | for (; i < text1.length(); i++) {
301 | Matcher mm = pattern.matcher(text1.charAt(i) + "");
302 | if (mm.find())
303 | break;
304 | }
305 | int end = i;
306 | end = cur + end;
307 | return end;
308 | }
309 |
310 |
311 | /**
312 | * Find the View closest to y.
313 | *
314 | * @param x
315 | * @param y
316 | * @return
317 | */
318 | EWListViewChildET findMotionView(int x, int y) {
319 | //是否从顶部开始find提高效率
320 | boolean isTopStart = y < getHeight() / 2;
321 | int childCount = getChildCount();
322 | if (childCount > 0) {
323 | if (isTopStart) {
324 | for (int i = 0; i < childCount; i++) {
325 | if (!(getChildAt(i) instanceof EWListViewChildET))
326 | return null;
327 | EWListViewChildET v = (EWListViewChildET) getChildAt(i);
328 | if (y <= v.getBottom()) {
329 | //特殊处理--更新EditText--相对自己的x,y
330 | v.y = y - v.getTop();
331 | v.x = x;
332 | Log.e("J", "ET-->y::" + y + "--updata->" + v.y);
333 | return v;
334 | }
335 | }
336 | } else {
337 | for (int i = childCount - 1; i >= 0; i--) {
338 | if (!(getChildAt(i) instanceof EWListViewChildET))
339 | return null;
340 | EWListViewChildET v = (EWListViewChildET) getChildAt(i);
341 | if (y >= v.getTop()) {
342 | v.y = y - v.getTop();
343 | v.x = x;
344 | Log.e("J", "ET-->y::" + y + "--updata->" + v.y);
345 | return v;
346 | }
347 | }
348 | }
349 | }
350 | return null;
351 | }
352 |
353 | // ----------------------------------------------------$放大镜
354 | private PopupWindow popup;
355 | private static final int WIDTH = 400;
356 | private static final int HEIGHT = 100;
357 | private static final long DELAY_TIME = 250;
358 | private Magnifier magnifier;
359 |
360 | private void initMagnifier() {
361 | BitmapDrawable resDrawable = (BitmapDrawable) context.getResources().getDrawable(R.mipmap.ic_launcher);
362 | resBitmap = resDrawable.getBitmap();
363 |
364 | magnifier = new Magnifier(context);
365 |
366 | //pop在宽高的基础上多加出边框的宽高
367 | popup = new PopupWindow(magnifier, WIDTH + 2, HEIGHT + 10);
368 | popup.setAnimationStyle(android.R.style.Animation_Toast);
369 |
370 | dstPoint = new Point(0, 0);
371 | }
372 |
373 | Runnable showZoom = new Runnable() {
374 | public void run() {
375 | popup.showAtLocation(EWListView.this,
376 | Gravity.NO_GRAVITY,
377 | getLeft() + dstPoint.x,
378 | getTop() + dstPoint.y);
379 | }
380 | };
381 |
382 |
383 | private Bitmap resBitmap;
384 | private Point dstPoint;
385 |
386 | class Magnifier extends View {
387 | private Paint mPaint;
388 |
389 | public Magnifier(Context context) {
390 | super(context);
391 | mPaint = new Paint();
392 | mPaint.setAntiAlias(true);
393 | mPaint.setColor(0xff008000);
394 | mPaint.setStyle(Paint.Style.STROKE);
395 | }
396 |
397 | @Override
398 | protected void onDraw(Canvas canvas) {
399 | canvas.save();
400 | // draw popup
401 | mPaint.setAlpha(255);
402 | canvas.drawBitmap(resBitmap, 0, 0, mPaint);
403 | canvas.restore();
404 |
405 | //draw popup frame
406 | mPaint.reset();//重置
407 | mPaint.setColor(Color.LTGRAY);
408 | mPaint.setStyle(Paint.Style.STROKE);//设置空心
409 | mPaint.setStrokeWidth(2);
410 | Path path1 = new Path();
411 | path1.moveTo(0, 0);
412 | path1.lineTo(WIDTH, 0);
413 | path1.lineTo(WIDTH, HEIGHT);
414 | path1.lineTo(WIDTH / 2 + 15, HEIGHT);
415 | path1.lineTo(WIDTH / 2, HEIGHT + 10);
416 | path1.lineTo(WIDTH / 2 - 15, HEIGHT);
417 | path1.lineTo(0, HEIGHT);
418 | path1.close();//封闭
419 | canvas.drawPath(path1, mPaint);
420 | }
421 | }
422 |
423 |
424 | private Bitmap bitmap;//生成的位图
425 | //截图
426 |
427 | /**
428 | * @param activity
429 | * @param x 截图起始的横坐标
430 | * @param y 截图起始的纵坐标
431 | * @param width
432 | * @param height
433 | * @return
434 | */
435 | private Bitmap getBitmap(Activity activity, int x, int y, int width, int height) {
436 | View view = activity.getWindow().getDecorView();
437 | view.setDrawingCacheEnabled(true);
438 | view.buildDrawingCache();
439 | bitmap = view.getDrawingCache();
440 | //边界处理,否则会崩滴
441 | if (x < 0)
442 | x = 0;
443 | if (y < 0)
444 | y = 0;
445 | if (x + width > bitmap.getWidth()) {
446 | // x = x + WIDTH / 2;
447 | // width = bitmap.getWidth() - x;
448 | //保持不改变,截取图片宽高的原则
449 | x = bitmap.getWidth() - width;
450 | }
451 | if (y + height > bitmap.getHeight()) {
452 | // y = y + HEIGHT / 2;
453 | // height = bitmap.getHeight() - y;
454 | y = bitmap.getHeight() - height;
455 | }
456 | Rect frame = new Rect();
457 | activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
458 | int toHeight = frame.top;
459 | bitmap = Bitmap.createBitmap(bitmap, x, y, width, height);
460 | view.setDrawingCacheEnabled(false);
461 | return bitmap;
462 | }
463 |
464 |
465 | public void onLongPressWord(String word, EWListViewChildET ewe) {
466 | if (!"".equals(word))
467 | Toast.makeText(context, word, Toast.LENGTH_SHORT).show();
468 | else {
469 | ewe.requestFocus();
470 | ewe.setFocusable(false);
471 | // ewe.setFocusableInTouchMode(false);
472 | }
473 | }
474 |
475 |
476 | }
477 |
--------------------------------------------------------------------------------
/src/main/java/com/jcodeing/extractwordview/widget/EWListViewChildET.java:
--------------------------------------------------------------------------------
1 | package com.jcodeing.extractwordview.widget;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.util.AttributeSet;
6 | import android.view.ContextMenu;
7 | import android.view.Gravity;
8 | import android.view.MotionEvent;
9 | import android.view.ViewDebug.ExportedProperty;
10 | import android.widget.EditText;
11 |
12 | /**
13 | * The MIT License (MIT)
14 | *
15 | * Copyright (c) 2015 Jcodeing
16 | *
17 | * Permission is hereby granted, free of charge, to any person obtaining a copy
18 | * of this software and associated documentation files (the "Software"), to deal
19 | * in the Software without restriction, including without limitation the rights
20 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 | * copies of the Software, and to permit persons to whom the Software is
22 | * furnished to do so, subject to the following conditions:
23 | *
24 | * The above copyright notice and this permission notice shall be included in all
25 | * copies or substantial portions of the Software.
26 | *
27 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 | * SOFTWARE.
34 | */
35 | public class EWListViewChildET extends EditText {
36 | /**
37 | * 相对自身的x,y
38 | */
39 | public int x, y;
40 |
41 | // ---------三个构造----------------------------------------------$构造
42 | // 当设置,指定样式时调用
43 | public EWListViewChildET(Context context, AttributeSet attrs, int defStyle) {
44 | super(context, attrs, defStyle);
45 | initialize(context);
46 | }
47 |
48 | // 布局文件初始化的时候,调用-------该构造方法,重用------------★
49 | // 布局文件里面定义的属性都放在 AttributeSet attrs
50 | public EWListViewChildET(Context context, AttributeSet attrs) {
51 | super(context, attrs);
52 | initialize(context);
53 | }
54 |
55 | // 该方法,一般,在代码中 new 该类的时候_使用
56 | public EWListViewChildET(Context context) {
57 | super(context);
58 | initialize(context);
59 | }
60 |
61 | // --------------------------------------------------------------$初始
62 | private void initialize(Context context) {
63 | setGravity(Gravity.CENTER);
64 | setBackgroundColor(Color.TRANSPARENT);// 背景透明-去掉底部输入框
65 | }
66 |
67 | @Override
68 | protected void onCreateContextMenu(ContextMenu menu) {
69 | // 不做任何处理,为了阻止长按的时候弹出上下文菜单
70 | }
71 |
72 |
73 | @Override
74 | public boolean onTouchEvent(MotionEvent event) {
75 | return false;
76 | }
77 |
78 | @Override
79 | public boolean getDefaultEditable() {
80 | return false;
81 | }
82 |
83 | // 其实当前控件并没有获得焦点,我只是欺骗Android系统,让Android系统以我获得焦点的方式去处理
84 | // 用于将该控件Add到其他View下,导致失去焦点.
85 | @Override
86 | @ExportedProperty(category = "focus")
87 | public boolean isFocused() {
88 | return super.isFocused();// return true一定有焦点
89 | }
90 | }
--------------------------------------------------------------------------------
/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/main/res/layout/ewlv_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jcodeing/ExtractWordView/877b544c298d29a1c73b7b840a44438b9ce70973/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ExtractWordView
3 |
4 | Hello world!
5 | Settings
6 |
7 | Awakening
8 |
9 | - Hold fast to dreams \n紧紧抓住梦想,
10 | - For if dreams die \n梦想若是消亡
11 | - Life is a broken-winged bird \n生命就象鸟儿折了翅膀
12 | - That can never fly. \n再也不能飞翔
13 | - Hold fast to dreams \n紧紧抓住梦想,
14 | - For when dreams go \n梦想若是消丧
15 | - Life is a barren field \n生命就象贫瘠的荒野,
16 | - Frozen only with snow v \n雪覆冰封,万物不再生
17 | - The world in the morning to get everything \n世界在清晨获取万物
18 | - And you lose the world in the early morning \n而你在清晨失去世界
19 | - Don\'t let the early morning and then go \n不要让清晨随之而去
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------