【问题标题】:How to override default text selection of android webview os 4.1+?如何覆盖android webview os 4.1+的默认文本选择?
【发布时间】:2013-06-06 03:04:03
【问题描述】:

在发布这个问题之前,我已经搜索了很多,但找不到关于这个问题的任何明确答案。

我必须覆盖 android webview 的默认文本选择并显示我的自定义文本选择对话框选项。我试过这个示例代码project

此示例项目适用于以下设备和模拟器:

  • Acer Iconia a500 平板电脑:10 英寸:Android OS - 3.0
  • Acer Iconia a500 平板电脑:10 英寸:Android OS - 3.2
  • 三星 Galaxy Tab:10 英寸:Android 操作系统 - 4.0
  • 三星 Galaxy Tab:7 英寸:Android 操作系统 - 4.0
  • 模拟器:Skin-WVGA800:Android OS - 4.1.2

不适用于以下设备:

  • 三星 Galaxy Tab:10 英寸:Android 操作系统 - 4.1.2
  • 三星 Galaxy Tab:7 英寸:Android 操作系统 - 4.1.2

在 android os 版本 4.1 和 4.1+ 上,它不显示我的自定义文本选择选项对话框,而是显示 android 系统用于文本选择的默认操作栏。

我对此进行了很多搜索,许多人建议使用interfaceonLongClick()方法

我已经在这个论坛上问过一个问题,请看这个link,这个问题的答案我可以克隆 onLongClick() 事件,但我无法停止默认的文本选择操作栏。

对于这种情况,我有几个问题。

1.为什么onLongClick()方法在android os 4.1+版本的设备上停止工作?

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);
}

}

提前致谢!

【问题讨论】:

  • 您能否发布一下您是如何实现 onLongClick() 的?在我的 GalaxyS3 (OS 4.1.2) 上,它运行没有问题,我成功地覆盖了它,所以我没有显示动作模式。
  • @gunar 我已经通过添加自定义 webview 代码编辑了我的问题,此代码具有 onLongClick 方法以及使用 longClickRunnable、longClickHandler 的 onLongClick 克隆、其余代码和支持类的 Drag & Drop 是来自 github.com/btate/BTAndroidWebViewSelection 的相同示例项目
  • @gunar 我在 GalaxyS3 OS 4.1.2 上测试了相同的代码,它运行良好,阻止了 webview 的文本选择模式并显示了我的默认自定义文本选择选项,但相同的代码在三星上不起作用OS 4.1.2 10 英寸平板电脑
  • 尝试使用反射在运行时调查WebView的API。三星平板电脑的 WebView 实现几乎没有什么不同。有一些自定义方法可以帮助您。我不记得确切的姓名或签名,我稍后会尝试这样做。
  • 嗨,Sachin 我在使用 webview 实现richtexteditor 时遇到了同样的问题。只是想知道你能找到任何解决方法......谢谢

标签: android android-webview textselection


【解决方案1】:

我知道这是一个老问题,但没有一个公认的答案,而且提供的答案都没有很多票。

我已经通过创建自定义操作模式回调实现了您想要实现的目标。这允许在(在WebView 的情况下)用户长按视图时创建自定义上下文操作栏 (CAB)。 注意: 这仅适用于 4.0+,一件仅适用于 4.4。

创建一个包含自定义菜单布局的新 Android XML 文件。这里以我的(context_menu.xml)为例:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/copy"
        android:icon="@drawable/ic_action_copy"
        android:showAsAction="always" <!-- Forces this button to be shown -->
        android:title="@string/copy">
    </item>
    <item
        android:id="@+id/button2"
        android:icon="@drawable/menu_button2icon"
        android:showAsAction="ifRoom" <!-- Show if there is room on the screen-->
        android:title="@string/button2">
    </item>
    <!-- Add as many more items as you want.
         Note that if you use "ifRoom", an overflow menu will populate
         to hold the action items that did not fit in the action bar. -->

</menu>

现在菜单已经定义好了,为它创建一个回调:

public class CustomWebView extends WebView {

    private ActionMode.Callback mActionModeCallback;

    private class CustomActionModeCallback implements ActionMode.Callback {

        // Called when the action mode is created; startActionMode() was called
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // Inflate a menu resource providing context menu items
            MenuInflater inflater = mode.getMenuInflater();
            inflater.inflate(R.menu.context_menu, menu);
            return true;
        }

        // Called each time the action mode is shown.
        // Always called after onCreateActionMode, but
        // may be called multiple times if the mode is invalidated.
        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            // Note: This is called every time the selection handlebars move.
            return false; // Return false if nothing is done
        }

        // Called when the user selects a contextual menu item
        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {

            switch (item.getItemId()) {
                case R.id.copy:
                // Do some stuff for this button
                   mode.finish(); // Action picked, so close the CAB
                   return true;
                case R.id.button2:
                // Do some stuff for this button
                   mode.finish();
                   return true;
                // Create a case for every item
                ...
                default:
                   mode.finish();
                   return false;
            }
        }

        // Called when the user exits the action mode
        @Override
        public void onDestroyActionMode(ActionMode mode) {
            // This will clear the selection highlight and handlebars.
            // However, it only works in 4.4; I am still investigating
            // how to reliably clear the selection in 4.3 and earlier
            clearFocus();
        }
}

一旦这些部分就位,覆盖 startActionMode 方法,以便回调将在长按时实际发生:

public class CustomWebView extends WebView {
    @Override
    public ActionMode startActionMode(Callback callback) {
        ViewParent parent = getParent();
        if (parent == null) {
        return null;
        }
        mActionModeCallback = new CustomActionModeCallback();
        return parent.startActionModeForChild(this, mActionModeCallback);
    }
}

现在您有一个自定义菜单,当用户长按您的WebView 时,该菜单将出现并填充。作为参考,我从Android Developer tutorial 中找到了此信息,并修改了this answer 中的建议。

最后一点:此技术会覆盖所选文本的本机复制/粘贴菜单。如果您想维护该功能,您将需要自己实现它。 (这就是为什么我的第一个按钮是“复制”。)如果您需要,请参阅this Google tutorial 了解更多信息以及实现它的正确方法。

【讨论】:

  • 很好的解决方案!几天后,我找到了你的帖子。谢谢+1
  • 我做了一个类似的解决方案,但是有一个问题。我有7个自定义项目,但是其中3个因为不在视图中而看不到。是否可以在文本选择菜单中滚动? @SciJoker
  • 如果用户从 webview 复制文本,如何打开自定义弹出窗口?请帮帮我:)
  • 此解决方案是否适用于 android 5 及更高版本,我尝试在自定义 webview 上实现它,显示操作栏但未捕获对项目的单击。有谁知道我错过了什么?
【解决方案2】:

我已经能够解决这个问题。我也遇到了这个问题,在网上找不到任何解决方案。

因此,如果您设置了 LongClick 侦听器,Webview 将完全停止显示选择。深入研究了Webview的代码,发现它调用了WebView的startRunMode方法,传递了一个SelectActionCallbackMode类的实例。

我只是扩展了 Webview 类并像这样覆盖了 startRunMode 方法:

public ActionMode startActionMode(ActionMode.Callback callback) 
{
    actionModeCallback = new CustomizedSelectActionModeCallback();
    return super.startActionMode(actionModeCallback);
}

这迫使 Webview 显示我的回调,而不是显示 Webview 的默认回调。这确保了选择像以前一样顺利,每次选择时都会显示我的 CAB。唯一需要注意的是,我必须自己编写代码来解除 CAB。

在 4.1、4.2 和 4.3 设备上测试。

希望这会有所帮助。

【讨论】:

    【解决方案3】:

    可以使用 setOnTouchListener() 进行长/短长按。并返回 true,这样就不会发生任何事情,从而覆盖默认的文本选择功能。

    【讨论】:

      【解决方案4】:

      使用setOnTouchListener()实现长按

      【讨论】:

        【解决方案5】:

        如果有人试图简单地删除默认文本选择,我在 Android 4.1.2 上的三星 Galaxy Tab 上遇到了同样的问题,最后我写了自己的WebView

        public class CustomWebView extends WebView {
        
          public CustomWebView(Context context) {
            super(context);
            this.setUp(context);
          }
        
          public CustomWebView(Context context, AttributeSet attrs) {
            super(context, attrs);
            this.setUp(context);
          }
        
          public CustomWebView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            this.setUp(context);
          }
        
          private void setUp(Context context) {
            this.setOnLongClickListener(new OnLongClickListener() {
              @Override
              public boolean onLongClick(View v) {
                return true;
              }
            });
            this.setLongClickable(false);
          }
        
          @Override
          public boolean performLongClick() {
            return true;
          }
        
        }
        

        并在我的 xml 中引用它:

        <com...CustomWebView
            android:id="@+id/webview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />
        

        【讨论】:

          【解决方案6】:

          您也可以尝试覆盖 View.performLongClick(),它负责调用 View.onLongPress()。您也可以尝试上到父视图的长按事件。或者一直到您的活动的 Window.Callback(通过 Activity.getWindow().get/setCallback())。

          我想知道除了长按事件之外,标准选择上下文菜单是否还有其他显示方式,例如可能使用轨迹球或硬件键盘。

          【讨论】:

            【解决方案7】:

            要禁用从 web 视图中选择文本,试试这个,

            webView.setWebChromeClient(new WebChromeClient(){
            
                [.... other overrides....]
            
                // @Override
                // https://bugzilla.wikimedia.org/show_bug.cgi?id=31484
                // If you DO NOT want to start selection by long click,
                // the remove this function
                // (All this is undocumented stuff...)
                public void onSelectionStart(WebView view) {
                    // By default we cancel the selection again, thus disabling
                    // text selection unless the chrome client supports it.
                    // view.notifySelectDialogDismissed();
                }
            
            });
            

            【讨论】:

            • 我试图覆盖 onSelectionStart 方法,但没有这样的方法可以覆盖,我使用了复制粘贴 & eclipse 给了我“来自类型 new WebChromeClient(){} 的方法 onSelectionStart(WebView) 永远不会本地使用”
            【解决方案8】:

            由于某些原因,KeyEvent down & up 在 API LEVEL 16+(Android 4.1+ JELLY_BEAN)中不起作用。它不会触发 WebView 的 loadUrl。所以我不得不用dispatchTouchEvent替换dispatchKeyEvent。代码如下:

            ...
            MotionEvent touchDown = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, touchX, touchY, 0);
            webView.dispatchTouchEvent(touchDown);
            touchDown.recycle();
            
            MotionEvent touchUp = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, touchX, touchY, 0);
            webView.dispatchTouchEvent(touchUp);
            touchUp.recycle();
            
            String url = mUrl;
            ...
            

            试试吧..

            【讨论】:

              【解决方案9】:

              我可以为此建议一个解决方法。 您可以使用setOnTouchListener,然后自己实现长按。

              onTouch --
              
               case MotionEvent.ACTION_DOWN:  
                                  mHanlder.removeCallbacks(startActionBar);
                                  mHandler.postDelayed(startActionBar,1000);
              

              这样你就可以达到同样的效果。

              【讨论】:

              • 这部分没问题,但它也显示了默认的上下文操作栏 (CAB) 并调整了 webview 的大小,在我的情况下,我想避免使用默认的 CAB。
              • 禁用长按网页浏览。 this.setLongClickable(false);
              • @blganesh101 它可能适用于大多数设备,但不适用于问题中指定的 Android 4.1.2 上的三星 Galaxy Tab
              猜你喜欢
              • 2013-02-28
              • 2012-05-16
              • 2014-10-20
              • 2015-09-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-10-27
              相关资源
              最近更新 更多