web-dev-qa-db-ja.com

androidでWebビューのテキスト選択メニューをオーバーライドする方法

基本的なAndroidのWebテキスト選択メニューは、以下の画像に示すとおりです。コピー、共有、すべて選択、ウェブ検索などのオプションがあります。

enter image description here

私はこのメニューを乗り越えて、「マークカラー」、「インプとしてマーク」などの独自のメニューリストとしてそれらを望んでいます。スタックオーバーフローのコンテキストメニューについて利用可能な質問のほとんどを見回します。質問のほとんどはコンテキストメニューに関連していますが、期待どおりの結果が得られません。下の画像のようなメニューが欲しい

enter image description here

選択を実行すると、Androidモニターがビュー作成フォームviewRootのようないくつかを表示します

D/ViewRootImpl: #1 mView = Android.widget.PopupWindow$PopupDecorView{648898f V.E...... ......I. 0,0-0,0}
D/ViewRootImpl: #1 mView = Android.widget.PopupWindow$PopupDecorView{a66541c V.E...... ......I. 0,0-0,0}
D/ViewRootImpl: MSG_RESIZED_REPORT: ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1
D/ViewRootImpl: MSG_RESIZED_REPORT: ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1

このような実装を実現するにはどうすればよいですか?

https://github.com/naoak/WebViewMarker も試してみましたが、適切な結果が得られませんでした。

私がまだやったことは?

Android=のWebViewを拡張し、最低限のSDK 19をサポートしたい.

19
Abhishek

このソリューションは、アクティビティのアクションモードに依存せず、すべての人に機能しますAndroid platform

回答しようとしましたが、文字数制限を超えているため、コード部分を入れています

Webビューでの選択用の参照リンク1

https://github.com/btate/BTAndroidWebViewSelection

Webビューマーカーを作成するための参照リンク2

https://github.com/liufsd/WebViewMarker

上記の両方のリンクは本当に重要な役割を果たし、一部の素晴らしい開発者によって開発されました。まず、参照リンクからのTextSelectionSupportクラスに関する調査が必要です。ここでは、Selection Listenerで選択範囲の四角形を取得するために、TextSelectionSupportクラスの2行のコードをカスタマイズしました。

ここからサンプルプロジェクトのクローンを作成 https://github.com/ab-cse-2014/WebViewSelection.git

CustomWebViewの実装およびTextSelectionSupportクラスの使用を参照してください。

これはプロジェクトの私のWebビュークラスです

 import Android.content.Context;
 import Android.graphics.Rect;
 import Android.os.Build;
 import Android.support.annotation.RequiresApi;
 import Android.support.v7.app.AppCompatActivity;
 import Android.util.AttributeSet;
 import Android.util.Log;
 import Android.view.Gravity;
 import Android.view.LayoutInflater;
 import Android.view.View;
 import Android.webkit.WebView;
 import Android.widget.PopupWindow;
 import Android.widget.Toast;

 import com.cse.webviewtextselection.R;
 import com.cse.webviewtextselection.webviewmaker.TextSelectionSupport;

 public class CustomWebView extends WebView {

private final String TAG = this.getClass().getSimpleName();

private Context mContext;

private TextSelectionSupport mTextSelectionSupport;

private PopupWindow mPopupWindow;

private int currentTop;

public CustomWebView(Context context) {
    super(context);
    mContext = context;
    initSetUp();
    preparePopupWindow();
}

public CustomWebView(Context context, AttributeSet attrs) {
    super(context, attrs);
    mContext = context;
    initSetUp();
    preparePopupWindow();
}

public CustomWebView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mContext = context;
    initSetUp();
    preparePopupWindow();
}

@RequiresApi(api = Build.VERSION_CODES.Lollipop)
public CustomWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    mContext = context;
    initSetUp();
    preparePopupWindow();
}

private void initSetUp() {

    mTextSelectionSupport = TextSelectionSupport.support((AppCompatActivity) mContext, this);
    mTextSelectionSupport.setSelectionListener(new TextSelectionSupport.SelectionListener() {
        @Override
        public void startSelection() {

        }

        @Override
        public void selectionChanged(String text, Rect rect) {
            Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
            showPopAtLocation(mPopupWindow, rect.left, rect.top);
        }

        @Override
        public void endSelection() {
            if (mPopupWindow != null) {
                mPopupWindow.dismiss();
            }
        }
    });
}

private void preparePopupWindow() {

    LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View customPopupView =  layoutInflater.inflate(R.layout.custom_popup_layout, null);
    mPopupWindow = new PopupWindow(customPopupView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);
    mPopupWindow.setAnimationStyle(Android.R.style.Animation_Dialog);

}

private void showPopAtLocation(PopupWindow mPopupWindow, int x, int y) {

    if (mPopupWindow != null) {

        if (currentTop != 0 || currentTop > ((AppCompatActivity)mContext).getWindow().getDecorView().getHeight()) {

                if (y > currentTop) {

                    y -= currentTop;

                }

        }

        Log.d("Current Top : ", String.valueOf(currentTop));
        Log.d("Y : ", String.valueOf(y));

        //mPopupWindow.showAtLocation(((AppCompatActivity)mContext).findViewById(R.id.parentRelativeLayout), Gravity.NO_GRAVITY, x, y);
        mPopupWindow.showAtLocation(((AppCompatActivity)mContext).getWindow().getDecorView(), Gravity.NO_GRAVITY, x, y);


    }

}

@Override
protected void onScrollChanged(int newLeft, int newTop, int oldLeft, int oldTop) {

    currentTop = newTop;


    super.onScrollChanged(newLeft, newTop, oldLeft, oldTop);
}
 }

Androids smart text selection(custom_popup_layout.xml)のようなカスタムポップアップメニューXML

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:id="@+id/myCustomMenuLinearLayout"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:orientation="horizontal"
Android:background="@Android:color/transparent">

<LinearLayout
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:orientation="horizontal"
    Android:background="@Android:color/white"
    Android:elevation="5dp"
    Android:layout_margin="12dp">

    <TextView
        Android:id="@+id/menuOne"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:text="Mark With Color"
        Android:textColor="@Android:color/black"
        Android:padding="10dp"
        Android:maxLines="1"/>

    <TextView
        Android:id="@+id/menuTwo"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:text="Mark As Important"
        Android:textColor="@Android:color/black"
        Android:padding="10dp"
        Android:maxLines="1"/>

    <TextView
        Android:id="@+id/menuThree"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:text="Show More"
        Android:textColor="@Android:color/black"
        Android:padding="10dp"
        Android:maxLines="1"/>

</LinearLayout>



</LinearLayout>

出力スクリーンショット

Selection One

Selection Two

Selection Three

2
Abhishek

アクティビティのアクションメニューを上書きする必要があります

あなたが読むことができるより多くの情報: https://developer.Android.com/guide/topics/ui/menus.html

上書きする方法:

@Override
public void onActionModeStarted(Android.view.ActionMode mode) {
    mode.getMenu().clear();
    Menu menus = mode.getMenu();
    mode.getMenuInflater().inflate(R.menu.highlight,menus);
    super.onActionModeStarted(mode);
}

ハイライト

    <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <item Android:id="@+id/impclick"
        Android:title="Mark As Important"
      />
    <item Android:id="@+id/colorclick"
        Android:title="Mark with color" />
</menu>
7

あなたが必要なのは、活動中のアクションモードです:

    @Override
    public void onActionModeStarted(ActionMode mode) {
        Menu menu = mode.getMenu();

        // you can remove original menu: copy, cut, select all, share ... or not
        menu.clear();

        // here i will get text selection by user
        menu.add(R.string.action_menu_preview_card)
                .setEnabled(true)
                .setVisible(true)
                .setOnMenuItemClickListener(item -> {
                    if (mWebview != null) {
                        mWebview.evaluateJavascript("window.getSelection().toString()", value -> {
                            value = StringUtil.trimToNull(value);
                            if (value != null) {
                                // do something about user select
                            }
                        });
                    }
                    mode.finish();
                    return true;
                });
        super.onActionModeStarted(mode);
    }

Android 21の上でテストしています。これはアクションモードメニューのクリックを処理でき、mode.getMenuInflater()。inflate(...)はそれを実行できません。

3
LinWei

私は here があなたを助けることができると思います。

完全を期すために、問題を修正する方法を次に示します。

私はこの回答に従って提案に従いましたが、オーバーライドされたコードにさらに厳密に一致するように少し調整しました。

パブリッククラスMyWebViewはWebViewを拡張します{

private ActionMode mActionMode;
private mActionMode.Callback mActionModeCallback;

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

}

基本的に、これにより、カスタマイズされたCABがAndroid CABの代わりに表示されます。ここで、コールバックを変更して、テキストの強調表示がCABと共に消えるようにする必要があります。

パブリッククラスMyWebViewはWebViewを拡張します{...プライベートクラスCustomActionModeCallbackはActionMode.Callbackを実装します{... //この時点までのすべてが質問と同じです

    // Called when the user exits the action mode
    @Override
    public void onDestroyActionMode(ActionMode mode) {
        clearFocus(); // This is the new code to remove the text highlight
         mActionMode = null;
    }
}

}

これですべてです。オーバーライドされたstartActionModeでMyWebViewを使用している限り、ネイティブCAB(WebViewの場合はコピー/貼り付けメニュー)を取得する方法がないことに注意してください。そのような動作を実装することは可能かもしれませんが、それはこのコードが機能する方法ではありません。更新:これを行うにははるかに簡単な方法があります!上記の解決策はうまく機能しますが、これは別の簡単な方法です。

このソリューションでは、ActionModeに対する制御が少なくなりますが、上記のソリューションよりはるかに少ないコードで済みます。

パブリッククラスMyActivityはActivity {

private ActionMode mActionMode = null;

@Override
public void onActionModeStarted(ActionMode mode) {
    if (mActionMode == null) {
        mActionMode = mode;
        Menu menu = mode.getMenu();
        // Remove the default menu items (select all, copy, paste, search)
        menu.clear();

        // If you want to keep any of the defaults,
        // remove the items you don't want individually:
        // menu.removeItem(Android.R.id.[id_of_item_to_remove])

        // Inflate your own menu items
        mode.getMenuInflater().inflate(R.menu.my_custom_menu, menu);
    }

    super.onActionModeStarted(mode);
}

// This method is what you should set as your item's onClick
// <item Android:onClick="onContextualMenuItemClicked" />
public void onContextualMenuItemClicked(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.example_item_1:
            // do some stuff
            break;
        case R.id.example_item_2:
            // do some different stuff
            break;
        default:
            // ...
            break;
    }

    // This will likely always be true, but check it anyway, just in case
    if (mActionMode != null) {
        mActionMode.finish();
    }
}

@Override
public void onActionModeFinished(ActionMode mode) {
    mActionMode = null;
    super.onActionModeFinished(mode);
}

}

ここにあなたが始めるためのメニューの例があります:

<item
    Android:id="@+id/example_item_1"
    Android:icon="@drawable/ic_menu_example_1"
    Android:showAsAction="always"
    Android:onClick="onContextualMenuItemClicked"
    Android:title="@string/example_1">
</item>

<item
    Android:id="@+id/example_item_2"
    Android:icon="@drawable/ic_menu_example_2"
    Android:showAsAction="ifRoom"
    Android:onClick="onContextualMenuItemClicked"
    Android:title="@string/example_2">
</item>

それでおしまい!完了です!これでカスタムメニューが表示され、選択を気にする必要がなくなり、ActionModeのライフサイクルを気にする必要がほとんどなくなります。

これは、親アクティビティ全体を占めるWebViewでほぼ完璧に機能します。アクティビティ内に一度に複数のビューがある場合、どれだけうまく機能するかわかりません。その場合は、多少の調整が必要になる可能性があります。

1
White Druid