在发布这个问题之前,我已经搜索了很多,但是没有找到关于这个问题的清晰答案。
我必须覆盖Android WebView的默认文本选择,并显示我的自定义文本选择对话框选项。我尝试过这个示例代码project。
此示例项目适用于以下设备和模拟器:
- Acer Iconia a500平板电脑:10英寸:Android操作系统-3.0
- Acer Iconia a500平板电脑:10英寸:Android操作系统-3.2
- Samsung Galaxy Tab:10英寸:Android操作系统-4.0
- Samsung Galaxy Tab:7英寸:Android操作系统-4.0
- 模拟器:Skin-WVGA800:Android操作系统-4.1.2
以下设备不适用:
- Samsung Galaxy Tab:10英寸:Android操作系统-4.1.2
- Samsung Galaxy Tab:7英寸:Android操作系统-4.1.2
我已经搜索了很多,许多人建议使用interface的onLongClick()方法。
我已经在这个论坛上提出了一个问题,请看link,通过这些问题的答案,我能够克隆onLongClick()事件,但我无法停止默认的文本选择动作栏。
针对这种情况,我有几个问题。
1.为什么在运行Android操作系统版本4.1+的设备上,onLongClick()方法会停止工作?
2.如何在长按Webview中的文本时停止默认的文本选择动作栏?
这是我的自定义Webview类。
package com.epubreader.ebook;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Display;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.epubreader.R;
import com.epubreader.drag.DragController;
import com.epubreader.drag.DragLayer;
import com.epubreader.drag.DragListener;
import com.epubreader.drag.DragSource;
import com.epubreader.drag.MyAbsoluteLayout;
import com.epubreader.menu.menuAnimationHelper;
import com.epubreader.textselection.WebTextSelectionJSInterface;
import com.epubreader.textselectionoverlay.ActionItem;
import com.epubreader.textselectionoverlay.QuickAction;
import com.epubreader.textselectionoverlay.QuickAction.OnDismissListener;
public class CustomWebView extends WebView implements WebTextSelectionJSInterface,
OnTouchListener , OnLongClickListener, OnDismissListener, DragListener{
/** The logging tag. */
private static final String TAG = "CustomWebView";
/** Context. */
protected Context ctx;
/** The context menu. */
private QuickAction mContextMenu;
/** The drag layer for selection. */
private DragLayer mSelectionDragLayer;
/** The drag controller for selection. */
private DragController mDragController;
/** The start selection handle. */
private ImageView mStartSelectionHandle;
/** the end selection handle. */
private ImageView mEndSelectionHandle;
/** The selection bounds. */
private Rect mSelectionBounds = null;
/** The previously selected region. */
protected Region lastSelectedRegion = null;
/** The selected range. */
protected String selectedRange = "";
/** The selected text. */
protected String selectedText = "";
/** Javascript interface for catching text selection. */
/** Selection mode flag. */
protected boolean inSelectionMode = false;
/** Flag to stop from showing context menu twice. */
protected boolean contextMenuVisible = false;
/** The current content width. */
protected int contentWidth = 0;
/** Identifier for the selection start handle. */
private final int SELECTION_START_HANDLE = 0;
/** Identifier for the selection end handle. */
private final int SELECTION_END_HANDLE = 1;
/** Last touched selection handle. */
private int mLastTouchedSelectionHandle = -1;
/** Variables for Left & Right Menu ***/
private View menuView;
private LinearLayout toiLay;
private menuAnimationHelper _menuAnimationHelper;
private TocTranslateAnimation _tocTranslateAnimation;
private CustomWebView _customWebView;
public CustomWebView(Context context) {
super(context);
this.ctx = context;
this.setup(context);
}
public CustomWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.ctx = context;
this.setup(context);
}
public CustomWebView(Context context, AttributeSet attrs) {
super(context, attrs);
this.ctx = context;
this.setup(context);
}
//*****************************************************
//*
//* Touch Listeners
//*
//*****************************************************
private boolean mScrolling = false;
private float mScrollDiffY = 0;
private float mLastTouchY = 0;
private float mScrollDiffX = 0;
private float mLastTouchX = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
float xPoint = getDensityIndependentValue(event.getX(), ctx) / getDensityIndependentValue(this.getScale(), ctx);
float yPoint = getDensityIndependentValue(event.getY(), ctx) / getDensityIndependentValue(this.getScale(), ctx);
// TODO: Need to update this to use this.getScale() as a factor.
//Log.d(TAG, "onTouch " + xPoint + " , " + yPoint);
closeMenu();
if(event.getAction() == MotionEvent.ACTION_DOWN){
final String startTouchUrl = String.format("javascript:android.selection.startTouch(%f, %f);",
xPoint, yPoint);
mLastTouchX = xPoint;
mLastTouchY = yPoint;
((Activity)this.ctx).runOnUiThread(new Runnable() {
@Override
public void run() {
loadUrl(startTouchUrl);
}
});
// This two line clones the onLongClick()
longClickHandler.removeCallbacks(longClickRunnable);
longClickHandler.postDelayed(longClickRunnable,300);
}
else if(event.getAction() == MotionEvent.ACTION_UP){
// Check for scrolling flag
if(!mScrolling){
this.endSelectionMode();
}
// This line clones the onLongClick()
longClickHandler.removeCallbacks(longClickRunnable);
mScrollDiffX = 0;
mScrollDiffY = 0;
mScrolling = false;
}else if(event.getAction() == MotionEvent.ACTION_MOVE){
mScrollDiffX += (xPoint - mLastTouchX);
mScrollDiffY += (yPoint - mLastTouchY);
mLastTouchX = xPoint;
mLastTouchY = yPoint;
// Only account for legitimate movement.
if(Math.abs(mScrollDiffX) > 10 || Math.abs(mScrollDiffY) > 10){
mScrolling = true;
}
// This line clones the onLongClick()
longClickHandler.removeCallbacks(longClickRunnable);
}
// If this is in selection mode, then nothing else should handle this touch
return false;
}
/**
* Pass References of Left & Right Menu
*/
public void initMenu(LinearLayout _toiLay,View _menuView,menuAnimationHelper menuAnimationHelper,
TocTranslateAnimation tocTranslateAnimation){
toiLay = _toiLay;
menuView = _menuView;
_menuAnimationHelper = menuAnimationHelper;
_tocTranslateAnimation = tocTranslateAnimation;
}
private void closeMenu(){
if(_menuAnimationHelper != null && _menuAnimationHelper.isMenuOpenBool){
_menuAnimationHelper.close(menuView);
_menuAnimationHelper.isMenuOpenBool = false;
}
if(_tocTranslateAnimation != null && _tocTranslateAnimation.isTocListOpenBool){
_tocTranslateAnimation.close(toiLay);
_tocTranslateAnimation.isTocListOpenBool = false;
}
}
public void removeOverlay(){
Log.d("JsHandler", "in java removeOverlay" + mScrolling);
this.endSelectionMode();
mScrollDiffX = 0;
mScrollDiffY = 0;
mScrolling = false;
}
@Override
public boolean onLongClick(View v){
Log.d(TAG, "from webView onLongClick ");
mScrolling = true;
((Activity)this.ctx).runOnUiThread(new Runnable() {
@Override
public void run() {
loadUrl("javascript:android.selection.longTouch()");
}
});
Toast.makeText(ctx, "Long click is clicked ", Toast.LENGTH_LONG).show();
// Don't let the webview handle it
return true;
}
//*****************************************************
//*
//* Setup
//*
//*****************************************************
ContextMenu.ContextMenuInfo contextMenuInfo;
/**
* Setups up the web view.
* @param context
*/
protected void setup(Context context){
// On Touch Listener
this.setOnTouchListener(this);
this.setClickable(false);
this.setLongClickable(true);
this.setOnLongClickListener(this);
contextMenuInfo = this.getContextMenuInfo();
// Webview setup
this.getSettings().setJavaScriptEnabled(true);
this.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
// Create the selection handles
createSelectionLayer(context);
// Set to the empty region
Region region = new Region();
region.setEmpty();
_customWebView = this;
this.lastSelectedRegion = region;
}
/**
* To clone OnLongClick Listener because its not responding for version 4.1
*/
public Runnable longClickRunnable = new Runnable() {
public void run() {
longClickHandler.sendEmptyMessage(0);
}
};
public Handler longClickHandler = new Handler(){
public void handleMessage(Message m){
_customWebView.loadUrl("javascript:android.selection.longTouch();");
mScrolling = true;
}
};
public WebTextSelectionJSInterface getTextSelectionJsInterface(){
return this;
}
//*****************************************************
//*
//* Selection Layer Handling
//*
//*****************************************************
/**
* Creates the selection layer.
*
* @param context
*/
protected void createSelectionLayer(Context context){
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.mSelectionDragLayer = (DragLayer) inflater.inflate(R.layout.selection_drag_layer, null);
// Make sure it's filling parent
this.mDragController = new DragController(context);
this.mDragController.setDragListener(this);
this.mDragController.addDropTarget(mSelectionDragLayer);
this.mSelectionDragLayer.setDragController(mDragController);
this.mStartSelectionHandle = (ImageView) this.mSelectionDragLayer.findViewById(R.id.startHandle);
this.mStartSelectionHandle.setTag(new Integer(SELECTION_START_HANDLE));
this.mEndSelectionHandle = (ImageView) this.mSelectionDragLayer.findViewById(R.id.endHandle);
this.mEndSelectionHandle.setTag(new Integer(SELECTION_END_HANDLE));
OnTouchListener handleTouchListener = new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean handledHere = false;
final int action = event.getAction();
// Down event starts drag for handle.
if (action == MotionEvent.ACTION_DOWN) {
handledHere = startDrag (v);
mLastTouchedSelectionHandle = (Integer) v.getTag();
}
return handledHere;
}
};
this.mStartSelectionHandle.setOnTouchListener(handleTouchListener);
this.mEndSelectionHandle.setOnTouchListener(handleTouchListener);
}
/**
* Starts selection mode on the UI thread
*/
private Handler startSelectionModeHandler = new Handler(){
public void handleMessage(Message m){
if(mSelectionBounds == null)
return;
addView(mSelectionDragLayer);
drawSelectionHandles();
int contentHeight = (int) Math.ceil(getDensityDependentValue(getContentHeight(), ctx));
// Update Layout Params
ViewGroup.LayoutParams layerParams = mSelectionDragLayer.getLayoutParams();
layerParams.height = contentHeight;
layerParams.width = contentWidth;
mSelectionDragLayer.setLayoutParams(layerParams);
}
};
/**
* Starts selection mode.
*
* @param selectionBounds
*/
public void startSelectionMode(){
this.startSelectionModeHandler.sendEmptyMessage(0);
}
// Ends selection mode on the UI thread
private Handler endSelectionModeHandler = new Handler(){
public void handleMessage(Message m){
//Log.d("TableContentsWithDisplay", "in endSelectionModeHandler");
removeView(mSelectionDragLayer);
if(getParent() != null && mContextMenu != null && contextMenuVisible){
// This will throw an error if the webview is being redrawn.
// No error handling needed, just need to stop the crash.
try{
mContextMenu.dismiss();
}
catch(Exception e){
}
}
mSelectionBounds = null;
mLastTouchedSelectionHandle = -1;
try {
((Activity)ctx).runOnUiThread(new Runnable() {
@Override
public void run() {
loadUrl("javascript: android.selection.clearSelection();");
}
});
} catch (Exception e) {
// TODO: handle exception
}
}
};
/**
* Ends selection mode.
*/
public void endSelectionMode(){
this.endSelectionModeHandler.sendEmptyMessage(0);
}
/**
* Calls the handler for drawing the selection handles.
*/
private void drawSelectionHandles(){
this.drawSelectionHandlesHandler.sendEmptyMessage(0);
}
/**
* Handler for drawing the selection handles on the UI thread.
*/
private Handler drawSelectionHandlesHandler = new Handler(){
public void handleMessage(Message m){
MyAbsoluteLayout.LayoutParams startParams = (com.epubreader.drag.MyAbsoluteLayout.LayoutParams) mStartSelectionHandle.getLayoutParams();
startParams.x = (int) (mSelectionBounds.left - mStartSelectionHandle.getDrawable().getIntrinsicWidth());
startParams.y = (int) (mSelectionBounds.top - mStartSelectionHandle.getDrawable().getIntrinsicHeight());
// Stay on screen.
startParams.x = (startParams.x < 0) ? 0 : startParams.x;
startParams.y = (startParams.y < 0) ? 0 : startParams.y;
mStartSelectionHandle.setLayoutParams(startParams);
MyAbsoluteLayout.LayoutParams endParams = (com.epubreader.drag.MyAbsoluteLayout.LayoutParams) mEndSelectionHandle.getLayoutParams();
endParams.x = (int) mSelectionBounds.right;
endParams.y = (int) mSelectionBounds.bottom;
endParams.x = (endParams.x < 0) ? 0 : endParams.x;
endParams.y = (endParams.y < 0) ? 0 : endParams.y;
mEndSelectionHandle.setLayoutParams(endParams);
}
};
/**
* Checks to see if this view is in selection mode.
* @return
*/
public boolean isInSelectionMode(){
return this.mSelectionDragLayer.getParent() != null;
}
//*****************************************************
//*
//* DragListener Methods
//*
//*****************************************************
/**
* Start dragging a view.
*
*/
private boolean startDrag (View v)
{
// Let the DragController initiate a drag-drop sequence.
// I use the dragInfo to pass along the object being dragged.
// I'm not sure how the Launcher designers do this.
Object dragInfo = v;
mDragController.startDrag (v, mSelectionDragLayer, dragInfo, DragController.DRAG_ACTION_MOVE);
return true;
}
@Override
public void onDragStart(DragSource source, Object info, int dragAction) {
// TODO Auto-generated method stub
}
@Override@SuppressWarnings("deprecation")
public void onDragEnd() {
// TODO Auto-generated method stub
MyAbsoluteLayout.LayoutParams startHandleParams = (MyAbsoluteLayout.LayoutParams) this.mStartSelectionHandle.getLayoutParams();
MyAbsoluteLayout.LayoutParams endHandleParams = (MyAbsoluteLayout.LayoutParams) this.mEndSelectionHandle.getLayoutParams();
float scale = getDensityIndependentValue(this.getScale(), ctx);
float startX = startHandleParams.x - this.getScrollX();
float startY = startHandleParams.y - this.getScrollY();
float endX = endHandleParams.x - this.getScrollX();
float endY = endHandleParams.y - this.getScrollY();
startX = getDensityIndependentValue(startX, ctx) / scale;
startY = getDensityIndependentValue(startY, ctx) / scale;
endX = getDensityIndependentValue(endX, ctx) / scale;
endY = getDensityIndependentValue(endY, ctx) / scale;
if(mLastTouchedSelectionHandle == SELECTION_START_HANDLE && startX > 0 && startY > 0){
final String saveStartString = String.format("javascript: android.selection.setStartPos(%f, %f);", startX, startY);
((Activity)ctx).runOnUiThread(new Runnable() {
@Override
public void run() {
loadUrl(saveStartString);
}
});
}
if(mLastTouchedSelectionHandle == SELECTION_END_HANDLE && endX > 0 && endY > 0){
final String saveEndString = String.format("javascript: android.selection.setEndPos(%f, %f);", endX, endY);
((Activity)ctx).runOnUiThread(new Runnable() {
@Override
public void run() {
loadUrl(saveEndString);
}
});
}
}
//*****************************************************
//*
//* Context Menu Creation
//*
//*****************************************************
/**
* Shows the context menu using the given region as an anchor point.
* @param region
*/
private void showContextMenu(Rect displayRect){
// Don't show this twice
if(this.contextMenuVisible){
return;
}
// Don't use empty rect
//if(displayRect.isEmpty()){
if(displayRect.right <= displayRect.left){
return;
}
//Copy action item
ActionItem buttonOne = new ActionItem();
buttonOne.setTitle("HighLight");
buttonOne.setActionId(1);
//buttonOne.setIcon(getResources().getDrawable(R.drawable.menu_search));
//Highlight action item
ActionItem buttonTwo = new ActionItem();
buttonTwo.setTitle("Note");
buttonTwo.setActionId(2);
//buttonTwo.setIcon(getResources().getDrawable(R.drawable.menu_info));
ActionItem buttonThree = new ActionItem();
buttonThree.setTitle("Help");
buttonThree.setActionId(3);
//buttonThree.setIcon(getResources().getDrawable(R.drawable.menu_eraser));
// The action menu
mContextMenu = new QuickAction(this.getContext());
mContextMenu.setOnDismissListener(this);
// Add buttons
mContextMenu.addActionItem(buttonOne);
mContextMenu.addActionItem(buttonTwo);
mContextMenu.addActionItem(buttonThree);
//setup the action item click listener
mContextMenu.setOnActionItemClickListener(new QuickAction.OnActionItemClickListener() {
@Override
public void onItemClick(QuickAction source, int pos,
int actionId) {
if (actionId == 1) {
callHighLight();
}
else if (actionId == 2) {
callNote();
}
else if (actionId == 3) {
// Do Button 3 stuff
Log.i(TAG, "Hit Button 3");
}
contextMenuVisible = false;
}
});
this.contextMenuVisible = true;
mContextMenu.show(this, displayRect);
}
private void callHighLight(){
((Activity)this.ctx).runOnUiThread(new Runnable() {
@Override
public void run() {
loadUrl("javascript:init_txt_selection_event()");
loadUrl("javascript:highlightme_("+0+")");
}
});
}
private void callNote(){
((Activity)this.ctx).runOnUiThread(new Runnable() {
@Override
public void run() {
loadUrl("javascript:init_txt_selection_event()");
loadUrl("javascript:fnGetUserAddedNote('1')");
}
});
}
//*****************************************************
//*
//* OnDismiss Listener
//*
//*****************************************************
/**
* Clears the selection when the context menu is dismissed.
*/
public void onDismiss(){
//clearSelection();
this.contextMenuVisible = false;
}
//*****************************************************
//*
//* Text Selection Javascript Interface Listener
//*
//*****************************************************
/**
* The user has started dragging the selection handles.
*/
public void tsjiStartSelectionMode(){
this.startSelectionMode();
}
/**
* The user has stopped dragging the selection handles.
*/
public void tsjiEndSelectionMode(){
this.endSelectionMode();
}
/**
* The selection has changed
* @param range
* @param text
* @param handleBounds
* @param menuBounds
* @param showHighlight
* @param showUnHighlight
*/@SuppressWarnings("deprecation")
public void tsjiSelectionChanged(String range, String text, String handleBounds, String menuBounds){
try {
//Log.d(TAG, "tsjiSelectionChanged :- handleBounds " + handleBounds);
JSONObject selectionBoundsObject = new JSONObject(handleBounds);
float scale = getDensityIndependentValue(this.getScale(), ctx);
Rect handleRect = new Rect();
handleRect.left = (int) (getDensityDependentValue(selectionBoundsObject.getInt("left"), getContext()) * scale);
handleRect.top = (int) (getDensityDependentValue(selectionBoundsObject.getInt("top"), getContext()) * scale);
handleRect.right = (int) (getDensityDependentValue(selectionBoundsObject.getInt("right"), getContext()) * scale);
handleRect.bottom = (int) (getDensityDependentValue(selectionBoundsObject.getInt("bottom"), getContext()) * scale);
this.mSelectionBounds = handleRect;
this.selectedRange = range;
this.selectedText = text;
JSONObject menuBoundsObject = new JSONObject(menuBounds);
Rect displayRect = new Rect();
displayRect.left = (int) (getDensityDependentValue(menuBoundsObject.getInt("left"), getContext()) * scale);
displayRect.top = (int) (getDensityDependentValue(menuBoundsObject.getInt("top") - 25, getContext()) * scale);
displayRect.right = (int) (getDensityDependentValue(menuBoundsObject.getInt("right"), getContext()) * scale);
displayRect.bottom = (int) (getDensityDependentValue(menuBoundsObject.getInt("bottom") + 25, getContext()) * scale);
if(!this.isInSelectionMode()){
this.startSelectionMode();
}
// This will send the menu rect
this.showContextMenu(displayRect);
drawSelectionHandles();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Receives the content width for the page.
*/
public void tsjiSetContentWidth(float contentWidth){
this.contentWidth = (int) this.getDensityDependentValue(contentWidth, ctx);
}
//*****************************************************
//*
//* Density Conversion
//*
//*****************************************************
/**
* Returns the density dependent value of the given float
* @param val
* @param ctx
* @return
*/
public float getDensityDependentValue(float val, Context ctx){
// Get display from context
Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
// Calculate min bound based on metrics
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
return val * (metrics.densityDpi / 160f);
}
/**
* Returns the density independent value of the given float
* @param val
* @param ctx
* @return
*/
public float getDensityIndependentValue(float val, Context ctx){
// Get display from context
Display display = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
// Calculate min bound based on metrics
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
return val / (metrics.densityDpi / 160f);
}
}
提前感谢!