38

For example I want to change the background-color of www.google.comto red. I have used webview, and my style.cssfile is in assest folder. I want to inject this style.css file to www.google.com. What is wrong with my codes? Please write the correct codes for me. Thanks. My MainActitviy.java file :

package com.example.mysina;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.WebView;

public class MainActivity extends ActionBarActivity {


        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            WebView webView = new WebView(this);

            setContentView(webView);

                    String html = "<html><head><style> src: url('file:///android_asset/style.css')</style></head></html>";

                    webView.loadData(html, "text/html", "utf-8");
                    webView.loadUrl("https://www.google.com");
        }
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
            if (id == R.id.action_settings) {
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
sinaamiri
  • 473
  • 2
  • 6
  • 9
  • I am doing the same you are doing. Just tell me where u add the css in your android project, and what your css files contains? i am stuck. Please help. – Uzair Qaiser Jun 26 '18 at 08:02

5 Answers5

67

You can't inject CSS directly however you can use Javascript to manipulate page dom.

public class MainActivity extends ActionBarActivity {

  WebView webView;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    webView = new WebView(this);
    setContentView(webView);

    // Enable Javascript
    webView.getSettings().setJavaScriptEnabled(true);

    // Add a WebViewClient
    webView.setWebViewClient(new WebViewClient() {

        @Override
        public void onPageFinished(WebView view, String url) {

            // Inject CSS when page is done loading
            injectCSS();
            super.onPageFinished(view, url);
        }
    });

    // Load a webpage
    webView.loadUrl("https://www.google.com");
}

// Inject CSS method: read style.css from assets folder
// Append stylesheet to document head
private void injectCSS() {
    try {
        InputStream inputStream = getAssets().open("style.css");
        byte[] buffer = new byte[inputStream.available()];
        inputStream.read(buffer);
        inputStream.close();
        String encoded = Base64.encodeToString(buffer, Base64.NO_WRAP);
        webView.loadUrl("javascript:(function() {" +
                "var parent = document.getElementsByTagName('head').item(0);" +
                "var style = document.createElement('style');" +
                "style.type = 'text/css';" +
                // Tell the browser to BASE64-decode the string into your script !!!
                "style.innerHTML = window.atob('" + encoded + "');" +
                "parent.appendChild(style)" +
                "})()");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
  }
}
Manish
  • 4,903
  • 1
  • 29
  • 39
  • Thanx. I have copied these codes to my project. Now I can change the style of the page. But there is a big problem. It can not load page. it says: Webpage not availabe. It does not open www.google.com . Which code I should change it? – sinaamiri May 03 '15 at 21:06
  • Search and read about WebViewClient and shouldOverrideUrlLoading method. Don't forget to include INTERNET permission in AndroidManifest file. – Manish May 03 '15 at 21:12
  • I want to inject JavaScript to my site, so It means that I can access to my site, how can I permit myself to inject JavaScript for full support of JavaScript. I asked the qustion [here](http://stackoverflow.com/questions/30036907/inject-javascript-file-to-my-site-with-webview-in-android). Thanks – sinaamiri May 05 '15 at 11:45
  • 1
    This is one of the best answers I've seen on this subject, but do be warned that there will be a little bit of a page jump if you switch styles. It would be wise to not show content until after the CSS injection is complete. – Muhammad Abdul-Rahim Oct 13 '15 at 20:41
  • 1
    Use DOM manipulation before loading Url in webview. By doing so you can inject any script or javascript file at runtime and then load the content into webview.I have been doing so in my ePub player and it works like charm. – frogEye Jun 21 '16 at 12:26
  • @Vishal Can you send me an example of it? – Mustansir Jul 23 '16 at 09:58
  • 2
    @Vishal would you mind adding an answer here with what you are talking about? – Matthew Clark Jun 08 '17 at 05:27
  • 5
    Why the base64 part? – neteinstein Feb 15 '18 at 23:15
  • You can use `WebView.evaluateJavascript()` now instead of having to use `loadUrl()` – Vishnu M. Aug 15 '18 at 01:32
10

I was able to inject css by using "evaluateJavascript" which was added to WebView in API 19 https://developer.android.com/reference/android/webkit/WebView

Example in Kotlin:

private lateinit var webView: WebView

override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    //...

    webView = findViewById(R.id.your_webview_name)

    webView.settings.javaScriptEnabled = true

    webView.webViewClient = object : WebViewClient() {

        override fun onPageFinished(view: WebView, url: String) {

            val css = ".menu_height{height:35px;}.. etc..." //your css as String
            val js = "var style = document.createElement('style'); style.innerHTML = '$css'; document.head.appendChild(style);"
            webView.evaluateJavascript(js,null)
            super.onPageFinished(view, url)
        }
    }

    webView.loadUrl("https://mywepage.com") //webpage you want to load   
}

UPDATE: The code above had issues applying all of the injected CSS. After consulting with my web developer, we decided to inject the link to the CSS file instead of the CSS code itself. I changed the values of the css and js variables ->

val css = "https://mywebsite.com/css/custom_app_styles.css"
val js = "var link = document.createElement('link'); link.setAttribute('href','$css'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('type','text/css'); document.head.appendChild(link);"
iOS_Mouse
  • 754
  • 7
  • 13
2

For Kotlin Users

import this

import android.util.Base64

and this is onPageFinished code

 override fun onPageFinished(view: WebView?, url: String?) {
                injectCSS()
}

and this is the code to call

private fun injectCSS() {
            try {
                val inputStream = assets.open("style.css")
                val buffer = ByteArray(inputStream.available())
                inputStream.read(buffer)
                inputStream.close()
                val encoded = Base64.encodeToString(buffer , Base64.NO_WRAP)
                webframe.loadUrl(
                    "javascript:(function() {" +
                            "var parent = document.getElementsByTagName('head').item(0);" +
                            "var style = document.createElement('style');" +
                            "style.type = 'text/css';" +
                            // Tell the browser to BASE64-decode the string into your script !!!
                            "style.innerHTML = window.atob('" + encoded + "');" +
                            "parent.appendChild(style)" +
                            "})()"
                )
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
Mohamed Slimane
  • 311
  • 3
  • 14
2

Here is the code snippet using Manish solution in Kotlin with a little bit UI/UX improve to avoid webview blinking/flash when inject css

Step 1: Define your css style which encoded to Base64

    companion object {
    private const val injectCss = """
                        Your CSS Style Go HERE
                        """

    private val styleCss =
            """
            javascript:(function() {
                    var parent = document.getElementsByTagName('head').item(0);
                    var style = document.createElement('style');
                    style.type = 'text/css';
                    style.innerHTML = window.atob('${stringToBase64(hideHeaderCss)}');
                    parent.appendChild(style)
                    })()
            """

    private fun stringToBase64(input: String): String {
        val inputStream: InputStream = ByteArrayInputStream(input.toByteArray(StandardCharsets.UTF_8))
        val buffer = ByteArray(inputStream.available())
        inputStream.read(buffer)
        inputStream.close()

        return android.util.Base64.encodeToString(buffer, android.util.Base64.NO_WRAP)
    }
}

Step 2: Your webview initialize state should be invisible

    <WebView
        android:id="@+id/wv_content"
        android:visibility="invisible"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

Step 3: Load webview with injected css, the trick here is only show webview when load complete

@SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewDataBinding.wvContent.loadUrl("Your URL Go Here")

    viewDataBinding.wvContent.settings.javaScriptEnabled = true
    viewDataBinding.wvContent.webViewClient = object : WebViewClient() {
        override fun onPageCommitVisible(view: WebView?, url: String?) {
            applyContentWithCSS()
            super.onPageCommitVisible(view, url)
        }

        override fun onPageFinished(view: WebView?, url: String?) {
            applyContentWithCSS()
            //Only show when load complete
            if (viewDataBinding.wvContent.progress == 100) {
                viewDataBinding.wvContent.smoothShow()
            }
            super.onPageFinished(view, url)
        }
    }
}

Step 4: Improve UX with a smooth transition to show webview

fun View.smoothShow() {
    this.apply {
        alpha = 0f
        visibility = View.VISIBLE

        animate()
            .alpha(1f)
            .setDuration(300)
            .setListener(null)
    }
}
NguyenDat
  • 4,129
  • 3
  • 46
  • 46
  • Thank you for the tip for preventing blinking and provide users with a better and seamless experience. – Ivan Lopes Aug 29 '21 at 17:57
  • 2
    I noticed that inserting `applyContentWithCSS` in `onPageStarted` handler makes the page display without blinking on newer Android versions and no animation is required. – Mike Oct 28 '21 at 22:30
0

Actually you can use WebViewClient.shouldInterceptRequest on API 11+. Example: webview shouldinterceptrequest example

Mygod
  • 2,077
  • 1
  • 19
  • 43