其实在不同版本上,webview底层是有所不同的 , 可能你会有webview中复制粘贴等功能的实现,而正常的API无法实现,这时你就需要看看webview的底层了,
先提供 API给大伙 : 当然,这是我在做某个项目上遇到的难点,先来看看收集的资料和自我总结。
今天弄了下android webview下的几个页面。原先以为android 4+把 webview的viewport属性忽略掉了。 但是今天弄了下。加了个 authorizationView.getSettings().setUseWideViewPort(true); viewport 的几个属性重新起作用。(测试环境,4.0+的几个版本) 但是又遇到几个问题,就是html里有input的时候。获取焦点的时候,android会重新缩放到原来模式,看源码: /** * Called in response to a message from webkit telling us that the soft * keyboard should be launched. */ private void displaySoftKeyboard( boolean isTextView) { InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); // bring it back to the default level scale so that user can enter text boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale(); if (zoom) { mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY); mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false ); } if (isTextView) { rebuildWebTextView(); if (inEditingMode()) { imm.showSoftInput(mWebTextView, 0 , mWebTextView.getResultReceiver()); if (zoom) { didUpdateWebTextViewDimensions(INTERSECTS_SCREEN); } return ; } } // Used by plugins and contentEditable. // Also used if the navigation cache is out of date, and // does not recognize that a textfield is in focus. In that // case, use WebView as the targeted view. // see http://b/issue?id=2457459 imm.showSoftInput( this , 0 ); } 从源码可以看到,webview当要弹起键盘的时候,会判定当前的缩放比例与默认大小(我测试了下,我自己的版本的默认值是1.5), 当你网页viewport设置initial-scale=0.5时,当input 获取焦点的时候,android会放大到原来模式,不是我们想要的,网上查了下相关, 有个解决方案: wv.setOnFocusChangeListener( new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { // TODO Auto-generated method stub try { Field defaultScale = WebView. class .getDeclaredField( " mDefaultScale " ); defaultScale.setAccessible( true ); float _s = defaultScale.getFloat(wv); defaultScale.setFloat(wv, scale); float x = wv.getScale(); int i = 0 ; } catch (Exception e) { e.printStackTrace(); try { Field defaultZoom = WebView. class .getDeclaredField( " mZoomManager " ); defaultZoom.setAccessible( true ); Field defaultScale = defaultZoom.getType() .getDeclaredField( " mDefaultScale " ); defaultScale.setAccessible( true ); defaultScale.setFloat(defaultZoom.get(wv), scale); } catch (Exception ee) { ee.printStackTrace(); } } } }); 但是作者碰到另外一个问题,引用自原话: as it showed, I using reflect to find the field ' mDefaultScale ' to control the WebView.But it doesn ' t work on Android 4.1.1 (Google Nexus), and I catch an exception —— java.lang.NoSuchFieldException: mDefaultScale. Then I list the fileds and found the framework source seems being changed(I can only reach a field called ' mProvider ' ).So how can I fix the problem (I haven ' t got the source yet)? Thanks for reading my question with my poor English, Thx :) PS: maybe a online framework source review website is helpful but I can ' t found one, if you can provide me one, it will be great. :P 完了我自己测试了,发现此方案解决不了。但是引出了另外一问题,就是不用android版本下的webview实现是不一样的,其实看代码就能看出, 原先webview有mDefaultScale字段,但是后来应该挪到mZoomManager里去了,但是我发现我手机上webview 实现和作者遇到的问题一样,只有mProvider成员, 没有mZoomManager,所以只能寻求源码,千辛万苦,终于找到 http://androidxref.com/4.2_r1/xref/frameworks/base/core/java/android/webkit/WebViewClassic.java,
mProvider 其实类型就是WebViewClassic(自己看下源码实现),简要提下,WebProvider只是一个接口,作为WebView的一个成员,
创建时用了factory来,完了看下几个工厂类,最后是webviewclassic实例)。
对于Jerry Bean 4.2这个版本(我一个手机就是自己刷的rom),webview的实现又换了个,所以要拿到默认缩放的成员,如下:
try { Field defaultScale = WebView. class .getDeclaredField( " mDefaultScale " ); defaultScale.setAccessible( true ); float sv = defaultScale.getFloat(authorizationView); defaultScale.setFloat(authorizationView, xxx); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); try { Field zoomManager; zoomManager = WebView. class .getDeclaredField( " mZoomManager " ); zoomManager.setAccessible( true ); Object zoomValue = zoomManager.get(authorizationView); Field defaultScale = zoomManager.getType().getDeclaredField( " mDefaultScale " ); defaultScale.setAccessible( true ); float sv = defaultScale.getFloat(zoomValue); defaultScale.setFloat(zoomValue, xxx); } catch (SecurityException e1) { e1.printStackTrace(); } catch (IllegalArgumentException e1) { e.printStackTrace(); } catch (IllegalAccessException e1) { e.printStackTrace(); } catch (NoSuchFieldException e1) { e1.printStackTrace(); try { Field mProviderField = WebView. class .getDeclaredField( " mProvider " ); mProviderField.setAccessible( true ); // mProviderField.getClass() Object webviewclassic = mProviderField.get(authorizationView); Field zoomManager = webviewclassic.getClass().getDeclaredField( " mZoomManager " ); zoomManager.setAccessible( true ); Object zoomValue = zoomManager.get(webviewclassic); Field defaultScale = zoomManager.getType().getDeclaredField( " mDefaultScale " ); defaultScale.setAccessible( true ); float sv = defaultScale.getFloat(zoomValue); defaultScale.setFloat(zoomValue, xxx); } catch (Exception e2) { e2.printStackTrace(); } } } 虽然可以拿到,并且设置成功,但是在我的手机上还是不能解决input 获取焦点后自动放大,
完了想了下,有个实现模式可以参考:当input 获取焦点时,js调用java设置默认放缩率,设置前保存原有值,失去焦点后重新设置原来值,不然跳转到其他页面的时候,你会发现比例不对了。:)。
因为放大后双击还是还原回原来样子。所以暂且不来纠结这个东西了。因为不同android版本的如果webview实现不一致的话,这代码就不起作用了 :)
————————————————————————————————————————————————————————————————————————————————————
用过EditText的都知道,EditText有个特点,当在里面长按的时候,会出现一个ContextMenu,提供了选择文字,复制,剪切等功能。有时候,我们会想,如果不出现这个ContextMenu,直接就在view上选择文字,那多美好啊。相信很多人抱有这样的想法,很不幸,我也是。于是我就研究了一下EditText和TextView的代码,然后将这个问题解决了。 网上很多资料都说,要选择一段文字,只需要用Selection.getSelectionStart()和Selection.getSelectionEnd()确定选择的文字的头和尾,然后加颜色就行。简直是胡扯啊,我敢说这样的代码根本就没有经过验证,就发到网上了,然后一大堆人互相转载,结果导致误导了很多人,杯具 啊!! 好,我们来分析一下解决办法。 TextView是很多View的基类,如Button、EditText都是继承自他,所以EditText里面的代码很少。我们看一下EditText的源码,有一个Override的getDefaultEditable方法,看名字的意思是是否可编辑,这个方法直接返回true。还有一个getDefaultMovementMethod方法,它返回的是ArrowKeyMovementMethod.getInstance(),通过查看ArrowKeyMovementMethod的源码,基本确定这个方法就是弹出ContextMenu和轨迹球监听的“元凶”。 下面,我们自己做一个view来打造自己的EditText。 我取名TextPage,继承EditText,在里面覆盖getDefaultEditable和getDefaultMovementMethod。
- @Override
- public boolean getDefaultEditable() {
- return false;
- }
- @Override
- protected MovementMethod getDefaultMovementMethod() {
- return null;
- }
现在测试一下,发现长按没反应了,所料不错,就是getDefaultMovementMethod方法控制了ContextMenu。 看一下ArrowKeyMovementMethod的代码,里面提供了KeyEvent、轨迹球事件onTrackballEvent和touch事件onTouchEvent的处理。这些事件在何处调用的呢?我们看看TextView的onTouchEvent、onTrackballEvent和onKeyEvent方法里面就明白了,在这些事件回调中调用了ArrowKeyMovementMethod里面的这些方法。 还有个问题,ContextMenu在哪里触发的?这个问题,用过ContextMenu的都知道,view里面要使用ContextMenu,需要覆盖一个onCreateContextMenu方法,然后在里面创建ContextMenu的各个选项。在TextView里面找onCreateContextMenu,果然有,里面定义了选择、复制、粘贴等选项。 既然找到了这个,那么我们就可以进一步分析选择是如何做到的。 onCreateContextMenu只是创建菜单,那么菜单点击之后,触发了什么呢?onCreateContextMenu里面定义了一个MenuHandler对象,然后作为参数传递给setOnMenuItemClickListener,找到MenuHandler,发现里面的onMenuItemClick返回的是onTextContextMenuItem函数,找到onTextContextMenuItem,OMG,终于找到点击menu触发的函数了。但是里面貌似没有关键的东西,选择的部分不在这里。那么,就应该在上面所说的那些事件里面了。 重点分析ArrowKeyMovementMethod的onTouchEvent方法。发现一个重要的方法getLayout(),然后获取一个Layout对象,通过x和y坐标知道当前字符串的offset位置。 那么,问题就可以完美的解决了。你可以点击任何地方然后拖动,释放之后,中间的文字就会被选中,so beautiful!
- import android.content.Context;
- import android.graphics.Color;
- import android.text.Layout;
- import android.text.Selection;
- import android.view.ContextMenu;
- import android.view.Gravity;
- import android.view.MotionEvent;
- import android.widget.EditText;
-
-
-
-
- public class TextPage extends EditText {
- private int off;
-
- public TextPage(Context context) {
- super(context);
- initialize();
- }
-
- private void initialize() {
- setGravity(Gravity.TOP);
- setBackgroundColor(Color.WHITE);
- }
-
- @Override
- protected void onCreateContextMenu(ContextMenu menu) {
-
- }
-
- @Override
- public boolean getDefaultEditable() {
- return false;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int action = event.getAction();
- Layout layout = getLayout();
- int line = 0;
- switch(action) {
- case MotionEvent.ACTION_DOWN:
- line = layout.getLineForVertical(getScrollY()+ (int)event.getY());
- off = layout.getOffsetForHorizontal(line, (int)event.getX());
- Selection.setSelection(getEditableText(), off);
- break;
- case MotionEvent.ACTION_MOVE:
- case MotionEvent.ACTION_UP:
- line = layout.getLineForVertical(getScrollY()+(int)event.getY());
- int curOff = layout.getOffsetForHorizontal(line, (int)event.getX());
- Selection.setSelection(getEditableText(), off, curOff);
- break;
- }
- return true;
- }
- }
最后为webview加上一点功能,为WebView加上复制文本功能 需求描述:
- 长按WebView出现Context menu,显示"复制”菜单
- 点击上述菜单后选择文本,复制到剪贴板
- 用OnTouchListener实现长按实现(参照android.view.View)
- 实现WebView的Context menu(在Activity实例中实现)
- 实现复制文本功能(兼容多个sdk)
编码:
- public class WebViewCopy {
- private Activity activity;
- private WebView webview;
- private static boolean mIsSelectingText;
-
- public static final String TAG=WebViewCopy.class.getSimpleName();
- public WebViewCopy(final Activity activity, final WebView webView){
- this.webview=webView;
- this.activity=activity;
- this.activity.registerForContextMenu(this.webview);
- webView.requestFocus(View.FOCUS_DOWN);
- webView.setOnTouchListener(new OnTouchListener() {
-
- boolean mHasPerformedLongPress;
- Runnable mPendingCheckForLongPress;
-
-
- @Override
- public boolean onTouch(final View v, MotionEvent event) {
-
-
-
- Log.d(TAG, "event:" + event.getAction());
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP:
- mIsSelectingText = false;
- if (!v.hasFocus()) {
- v.requestFocus();
- }
-
- if (!mHasPerformedLongPress) {
-
- if (mPendingCheckForLongPress != null) {
- v.removeCallbacks(mPendingCheckForLongPress);
- }
-
- }
-
- break;
- case MotionEvent.ACTION_DOWN:
-
- if (!v.hasFocus()) {
- v.requestFocus();
- }
-
- if( mPendingCheckForLongPress == null) {
-
-
- mPendingCheckForLongPress = new Runnable() {
- public void run() {
-
- if(! mIsSelectingText) {
- activity.openContextMenu(webview);
- mHasPerformedLongPress = true;
- mIsSelectingText = false;
- }
- }
- };
-
- }
-
-
- mHasPerformedLongPress = false;
- v. postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
-
- break;
-
- case MotionEvent.ACTION_MOVE:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
-
-
- int slop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
- if ((x < 0 - slop) || (x >= v.getWidth() + slop) ||
- (y < 0 - slop) || (y >= v.getHeight() + slop)) {
-
- if (mPendingCheckForLongPress != null) {
- v. removeCallbacks(mPendingCheckForLongPress);
- }
-
- }
- break;
- default:
- return false;
-
- }
-
- return false;
-
- }
- });
-
-
-
-
-
- }
-
- public static synchronized void emulateShiftHeld(WebView view)
- {
-
- try
- {
- KeyEvent shiftPressEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
- KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0);
- shiftPressEvent.dispatch(view);
- }
- catch (Exception e)
- {
- Log.e(TAG, "Exception in emulateShiftHeld()", e);
- }
- }
-
- public synchronized void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo,final int copy,String menuString) {
- MenuItem menuitem=menu.add(1, copy, Menu.NONE, menuString);
- menuitem.setOnMenuItemClickListener(new OnMenuItemClickListener() {
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- if(item.getItemId()==copy){
-
- selectAndCopyText(webview);
- }
- return false;
- }
- });
- }
- public static synchronized void selectAndCopyText(WebView v) {
- try {
-
- mIsSelectingText = true;
-
-
-
-
- if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.ECLAIR) {
- Method m = WebView.class.getMethod("emulateShiftHeld", Boolean.TYPE);
- m.invoke(v, false);
- }
- else {
- Method m = WebView.class.getMethod("emulateShiftHeld");
- m.invoke(v);
- }
-
- } catch (Exception e) {
-
- emulateShiftHeld(v);
- }finally{
-
- }
-
- }
- }
下面的代码在activity中写:
1) 在onCreate中生成 WebViewCopy 实例: copy = new WebViewCopy(this, _webView);
2) 在onCreateContextMenu中注入复制菜单public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- copy.onCreateContextMenu(menu, v, menuInfo,COPY,getString(R.string.copy));
- super.onCreateContextMenu(menu, v, menuInfo);
回顾与总结:
OnTouchListener可能在一些时候更本不响应,如Zoom Button出现后。这得让WebView重新获取焦点,
这是WebView又一已知的Bug. 整个难点在于重新获取焦点: webview.requestFocus();
参考:http://blog.csdn.net/djy1992/article/details/49406007