爲了可以讓簡書,掘金,CSDN,公衆號的文章展現成黑夜模式,須要webview作相關適配。原理其實也比較簡單,只要加載頁面時替換相關的css樣式作替換。實際實現效果每一個站點各有不一樣,下面就介紹下每一個站點是如何作實現的。javascript
簡書網站是有黑夜模式的,因此實現起來相對簡單。可是默認用webview加載簡書文章時,它顯示的是日間模式效果。打開chrome調試器,而後再簡書上切換黑夜模式,咱們能夠看到使用黑夜模式時,body
會有一個reader-night-mode
的class樣式加進去。 html
猜想簡書的黑夜模式和這個class樣式有關,那咱們能夠經過前端
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
複製代碼
調試webview,在chrome瀏覽器上輸入chrome://inspect
,而後就能夠調試web頁面了。咱們打開一篇簡書文章,經過調試器咱們將body
的樣式替換成reader-night-mode
,就會發現當前文章已經變成黑夜模式的了。 vue
爲了使閱讀體驗更好,咱們在打開文章時直接展開全文,同時去掉導航還有廣告等和文章內容無關的元素,咱們先經過調試器作測試。 java
經過剛剛的調試,發現這些效果對應的css樣式是在當前html頁面的head
標籤下,並非經過css文件形式。所以先經過OkHttp
請求文章地址生成html字符串,而後經過正則替換相關css。 先建立一個Wget
工具類,用於將網頁轉成字符串,這裏注意請求頭固定成移動設備。android
object Wget {
fun get(url: String): String {
val client = OkHttpClient.Builder()
.build()
val request = Request.Builder()
.url(url)
.header(
"user-agent",
"Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3887.7 Mobile Safari/537.36"
)
.build()
val response = client.newCall(request).execute()
return response.body()?.string() ?: ""
}
}
複製代碼
而後建立一個JianShuWebClient
,適配簡書css。那些寫在head
標籤下的樣式,經過觀察發現統一寫在了<style data-vue-ssr-id>
下面,咱們只需經過正則表達式找到它,而後replace替換咱們放在assets
下的css(拷貝自原style下的css,作了相關修改),而後將body
的樣式替換成reader-night-mode
。css3
class JianShuWebClient:WebViewClient(){
override fun shouldInterceptRequest(view: WebView?, url: String?)
: WebResourceResponse? {
val urlStr = url ?: ""
if (urlStr.startsWith("https://www.jianshu.com/p/")) {
val response = Wget.get(url ?: "")
val res = darkBody(replaceCss(response, view!!.context))
val input = ByteArrayInputStream(res.toByteArray())
return WebResourceResponse("text/html", "utf-8", input)
}
return super.shouldInterceptRequest(view, url)
}
private val rex = "(<style data-vue-ssr-id=[\\s\\S]*?>)([\\s\\S]*]?)(<\\/style>)"
private val bodyRex = "<body class=\"([\\ss\\S]*?)\""
private fun darkBody(res: String): String {
val pattern = Pattern.compile(bodyRex)
val m = pattern.matcher(res)
return if (m.find()) {
val s = "<body class=\"reader-night-mode normal-size\""
res.replace(bodyRex.toRegex(), s)
} else res
}
private fun replaceCss(res: String, context: Context): String {
val pattern = Pattern.compile(rex)
val m = pattern.matcher(res)
return if (m.find()) {
val css = StringUtil.getString(context.assets.open("jianshu/jianshu.css"))
val sb = StringBuilder()
sb.append(m.group(1))
sb.append(css)
sb.append(m.group(3))
val res = res.replace(rex.toRegex(), sb.toString())
Log.e("test", "$res")
res
} else {
res
}
}
}
複製代碼
掘金網站是沒有黑夜模式的(Android上有),所以適配起來相比簡書麻煩一些。與簡書不一樣的是,掘金文章的樣式是經過css文件外部引入的,因此就不須要OkHttp
請求轉換字符串了。咱們直接找到對應的文件在shouldInterceptRequest
方法中替換掉git
override fun shouldInterceptRequest(view: WebView?, url: String?)
: WebResourceResponse? {
Log.i("掘金", "url:$url")
val urlStr = url ?: ""
if (urlStr.startsWith("https://b-gold-cdn.xitu.io/v3/static/css/0")
&& urlStr.endsWith(".css")
) {
val stream = view!!.context.assets.open("juejin/css/juejin.css")
return WebResourceResponse("text/css", "utf-8", stream)
}
return super.shouldInterceptRequest(view, url)
}
複製代碼
經過插件能夠看到掘金前端是經過vue
編寫的,編譯的css會自帶[data-v-xxx]
的信息,每次更新時的xxx號碼會更高,咱們須要將[data]信息去除。參照簡書黑夜模式的樣式,咱們在juejin.css
加入黑夜模式的樣式。github
.article-area{padding:0 8px;background:#3f3f3f;color:#969696;}//背景,字體顏色
blockquote{background:#555;border-left:3px solid #222;margin:0px;padding:5px 16px;}//引用
code{color:#c7254e;border-radius:4px;background-color:#282828;padding:2px 4px;font-size:12px;}//代碼
.hljs {
display: block;
padding: 5px;
color: #abb2bf;
background: #282c34;
border-radius:4px;
font-size:12px;
}
.hljs-comment, .hljs-quote {//代碼關鍵字顏色
color: #5c6370;
font-style: italic
}
...還有不少,具體見項目
複製代碼
具體要注意的是背景顏色,文字顏色,代碼背景,顏色,引用,表格等等。
掘金文章的圖片是經過懶加載,使用替換的css,發現裏面的圖片顯示不了了。因此在頁面加載完成時注入圖片顯示腳本具體以下
val script = """ javascript:(function(){ var arr = document.getElementsByClassName("lazyload"); for(var i=0;i<arr.length;i++){ var img = arr[i]; var src = img.getAttribute("data-src"); img.src = src; } })(); """
webview.loadUrl(script)
複製代碼
頭像則經過接口獲取用戶數據而後,經過javascript修改。
private var head = ""
private var username = ""
private fun loadUser() {
val client = OkHttpClient.Builder().build()
val req = Request.Builder().url(detailApi).build()
val call = client.newCall(req)
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val res = response.body()?.string() ?: "{}"
val obj =
Gson().fromJson<JsonObject>(res, JsonObject::class.java)
obj?.getAsJsonObject("d")
?.getAsJsonObject("user")?.run {
head = get("avatarLarge").asString
username = get("username").asString
}
}
})
}
private fun getDetailApi(postId: String): String {//頭像沒有加載,手動調用
return "https://post-storage-api-ms.juejin" +
".im/v1/getDetailData?src=web&type=entry&postId=$postId"
}
fun loadUserScript() {
val script = """ javascript:(function(){ document.getElementsByClassName("author-info-block")[0].children[0].children[0].style.backgroundImage = "url('$head')"; document.getElementsByClassName("username")[0].innerHTML="$username"; })(); """.trimIndent()
webView.loadUrl(script)
}
複製代碼
一樣的CSDN的適配也掘金差很少,也是經過替換css文件完成的,這裏便再也不講述具體適配。
微信公衆號文章的樣式同簡書同樣也是放在當前html內部。正則表達式有所不一樣
val rex = "(<style>)([\\S ]*)(</style>)"
複製代碼
具體的意思是匹配style
標籤,而且內容包含字符,或者空格(換行不算)。
有些微信公衆號裏的文字標籤(如p
標籤)自己自帶了style樣式,很差經過正則替換。然而css3有一個!important
能夠提升優先級,強制設置相關標籤的屬性(即使它身設置了style樣式)。
固然important也不能濫用,不然一些你並不想改的樣式(如代碼)也都修改了,因此css選擇器要準確匹配纔可設置!important
。