cleaner titlewebview implementation

This commit is contained in:
Andrew Dolgov 2013-01-02 18:38:03 +04:00
parent 1d8a003fe1
commit 723fff03ec
6 changed files with 144 additions and 426 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.fox.ttrss" package="org.fox.ttrss"
android:versionCode="126" android:versionCode="127"
android:versionName="1.4" > android:versionName="1.5" >
<uses-sdk <uses-sdk
android:minSdkVersion="8" android:minSdkVersion="8"

View File

@ -2,8 +2,14 @@
android:id="@+id/article_fragment" android:id="@+id/article_fragment"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:padding="5sp" android:orientation="vertical"
android:orientation="vertical" > android:padding="5sp" >
<org.fox.ttrss.TitleWebView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" >
<LinearLayout <LinearLayout
android:id="@+id/article_header" android:id="@+id/article_header"
@ -14,20 +20,20 @@
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:textColor="?linkColor"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="There are many variations of passages of Lorem Ipsum available" android:text="There are many variations of passages of Lorem Ipsum available"
android:textColor="?linkColor"
android:textSize="16sp" /> android:textSize="16sp" />
<TextView <TextView
android:id="@+id/comments" android:id="@+id/comments"
android:textColor="?linkColor"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="24 comments" android:text="24 comments"
android:textColor="?linkColor"
android:textSize="13sp" /> android:textSize="13sp" />
<LinearLayout <LinearLayout
@ -40,10 +46,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0.7" android:layout_weight="0.7"
android:textColor="?headlineExcerptTextColor"
android:ellipsize="end" android:ellipsize="end"
android:singleLine="true" android:singleLine="true"
android:text="{TAGS}" android:text="{TAGS}"
android:textColor="?headlineExcerptTextColor"
android:textSize="13sp" /> android:textSize="13sp" />
<TextView <TextView
@ -53,63 +59,55 @@
android:layout_weight="0.2" android:layout_weight="0.2"
android:gravity="right" android:gravity="right"
android:text="{DATE}" android:text="{DATE}"
android:textColor="?headlineExcerptTextColor"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?headlineExcerptTextColor"
android:textSize="13sp" android:textSize="13sp"
android:textStyle="italic" /> android:textStyle="italic" />
</LinearLayout> </LinearLayout>
<ImageView <ImageView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="5sp"
android:layout_weight="0" android:layout_weight="0"
android:background="?horizontalDivider" android:background="?horizontalDivider"
android:layout_marginBottom="5sp"
android:paddingTop="2dip" /> android:paddingTop="2dip" />
</LinearLayout> </LinearLayout>
</org.fox.ttrss.TitleWebView>
<com.nobu_games.android.view.web.TitleBarWebView <LinearLayout
android:id="@+id/content" android:id="@+id/attachments_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="0" >
/>
<LinearLayout <Spinner
android:id="@+id/attachments_holder" android:id="@+id/attachments"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" > android:layout_weight="1"
android:prompt="@string/attachments_prompt" />
<Spinner <Button
android:id="@+id/attachments" android:id="@+id/attachment_view"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="0"
android:prompt="@string/attachments_prompt" /> android:text="@string/attachment_view" />
<Button <Button
android:id="@+id/attachment_view" android:id="@+id/attachment_share"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" android:layout_weight="0"
android:text="@string/attachment_view" /> android:text="@string/attachment_share" />
<Button <Button
android:id="@+id/attachment_share" android:id="@+id/attachment_copy"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" android:layout_weight="0"
android:text="@string/attachment_share" /> android:text="@string/attachment_copy" />
</LinearLayout>
<Button
android:id="@+id/attachment_copy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/attachment_copy" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -1,14 +0,0 @@
package android.webkit;
import android.view.View;
/**
* Trojan class for getting access to a hidden API level 16 interface
*/
public class WebViewClassic {
public interface TitleBarDelegate {
int getTitleHeight();
public void onSetEmbeddedTitleBar(final View title);
}
}

View File

@ -1,353 +0,0 @@
package com.nobu_games.android.view.web;
/*
* Copyright (C) 2012 Thomas Werner
* Portions Copyright (C) 2006 The Android Open Source Project
* Portions Copyright (C) 2012 The K-9 Dog Walkers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.lang.reflect.Method;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClassic.TitleBarDelegate;
import android.widget.FrameLayout;
/**
* WebView derivative with custom setEmbeddedTitleBar implementation for Android
* versions that do not support that feature anymore.
* <p>
* <b>Usage</b>
* <p>
* Call {@link #setEmbeddedTitleBarCompat(View)} for setting a view as embedded
* title bar on top of the displayed WebView page.
*/
public class TitleBarWebView extends WebView implements TitleBarDelegate {
/**
* Internally used view wrapper for suppressing unwanted touch events on the
* title bar view when WebView contents is being touched.
*/
private class TouchBlockView extends FrameLayout {
public TouchBlockView(Context context) {
super(context);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(!mTouchInTitleBar) {
return false;
} else {
switch(ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTouchInTitleBar = false;
break;
default:
}
return super.dispatchTouchEvent(ev);
}
}
}
private static final String TAG = "TitleBarWebView";
View mTitleBar;
int mTitleBarOffs;
boolean mTouchInTitleBar;
boolean mTouchMove;
private Rect mClipBounds = new Rect();
private Matrix mMatrix = new Matrix();
private Method mNativeGetVisibleTitleHeightMethod;
/**
* This will always contain a reference to the title bar view no matter if
* {@code setEmbeddedTitleBar()} or the Jelly Bean workaround is used. We use this in
* {@link #getTitleHeight()}.
*/
private View mTitleBarView;
public TitleBarWebView(Context context) {
super(context);
init();
}
public TitleBarWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TitleBarWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* <i>Corrects the visual displacement caused by the title bar view.</i>
* <p>
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if(mTitleBar != null) {
final int sy = getScrollY();
final int visTitleHeight = getVisibleTitleHeightCompat();
final float x = event.getX();
float y = event.getY();
switch(event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
if(y <= visTitleHeight) {
mTouchInTitleBar = true;
}
break;
case MotionEvent.ACTION_MOVE:
mTouchMove = true;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTouchMove = false;
break;
default:
}
if(mTouchInTitleBar) {
y += sy;
event.setLocation(x, y);
return mTitleBar.dispatchTouchEvent(event);
} else {
if(Build.VERSION.SDK_INT < 16) {
if(!mTouchMove) {
mTitleBarOffs = getVisibleTitleHeightCompat();
}
y -= mTitleBarOffs;
if(y < 0) y = 0;
event.setLocation(x, y);
}
return super.dispatchTouchEvent(event);
}
} else {
return super.dispatchTouchEvent(event);
}
}
/**
* Sets a {@link View} as an embedded title bar to appear on top of the
* WebView page.
* <p>
* This method tries to call the hidden API method setEmbeddedTitleBar if
* present. On failure the custom implementation provided by this class will
* be used instead.
*
* @param v The view to set or null for removing the title bar view.
*/
public void setEmbeddedTitleBarCompat(View v) {
try {
Method nativeMethod = getClass().getMethod("setEmbeddedTitleBar",
View.class);
nativeMethod.invoke(this, v);
} catch(Exception e) {
Log.d(TAG,
"Native setEmbeddedTitleBar not available. Starting workaround");
setEmbeddedTitleBarJellyBean(v);
}
mTitleBarView = v;
}
@Override
protected int computeVerticalScrollExtent() {
if(mTitleBar == null) return super.computeVerticalScrollExtent();
return getViewHeightWithTitle() - getVisibleTitleHeightCompat();
}
@Override
protected int computeVerticalScrollOffset() {
if(mTitleBar == null) return super.computeVerticalScrollOffset();
return Math.max(getScrollY() - getTitleHeight(), 0);
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
canvas.save();
if(child == mTitleBar) {
mTitleBar.offsetLeftAndRight(getScrollX() - mTitleBar.getLeft());
if(Build.VERSION.SDK_INT < 16) {
mMatrix.set(canvas.getMatrix());
mMatrix.postTranslate(0, -getScrollY());
canvas.setMatrix(mMatrix);
}
}
boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return result;
}
/**
* Gets the currently visible height of the title bar view if set.
*
* @return Visible height of title bar view or 0 if not set.
*/
protected int getVisibleTitleHeightCompat() {
if(mTitleBar == null && mNativeGetVisibleTitleHeightMethod != null) {
try {
return (Integer) mNativeGetVisibleTitleHeightMethod
.invoke(this);
} catch(Exception e) {
}
}
return Math.max(getTitleHeight() - Math.max(0, getScrollY()), 0);
}
@Override
protected void onDraw(Canvas canvas) {
if (Build.VERSION.SDK_INT >= 16) {
super.onDraw(canvas);
return;
}
canvas.save();
if(mTitleBar != null) {
final int sy = getScrollY();
final int sx = getScrollX();
mClipBounds.top = sy;
mClipBounds.left = sx;
mClipBounds.right = mClipBounds.left + getWidth();
mClipBounds.bottom = mClipBounds.top + getHeight();
canvas.clipRect(mClipBounds);
mMatrix.set(canvas.getMatrix());
int titleBarOffs = getVisibleTitleHeightCompat();
if(titleBarOffs < 0) titleBarOffs = 0;
mMatrix.postTranslate(0, titleBarOffs);
canvas.setMatrix(mMatrix);
}
super.onDraw(canvas);
canvas.restore();
}
/**
* Overrides a hidden method by replicating the behavior of the original
* WebView class from Android 2.3.4.
* <p>
* The worst that could happen is that this method never gets called, which
* isn't too bad because this does not harm the functionality of this class.
*/
protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
int l, int t, int r, int b) {
int sy = getScrollY();
if(sy < 0) {
t -= sy;
}
scrollBar.setBounds(l, t + getVisibleTitleHeightCompat(), r, b);
scrollBar.draw(canvas);
}
/**
* Get the height of the title bar view.
*
* <p>
* In the Jelly Bean workaround we need this method because we have to implement the
* {@link TitleBarDelegate} interface. But by implementing this method we override the hidden
* {@code getTitleHeight()} of the {@link WebView}s in older Android versions.
* <br>
* What we should do, is return the title height on Jelly Bean and call through to the parent
* class on older Android versions. But this would require even more trickery, so we just
* inline the parent functionality which simply calls {@link View#getHeight()}. This is exactly
* what we do on Jelly Bean anyway.
* </p>
*/
@Override
public int getTitleHeight() {
if (mTitleBarView != null) {
return mTitleBarView.getHeight();
}
return 0;
}
private int getViewHeightWithTitle() {
int height = getHeight();
if(isHorizontalScrollBarEnabled() && !overlayHorizontalScrollbar()) {
height -= getHorizontalScrollbarHeight();
}
return height;
}
private void init() {
try {
mNativeGetVisibleTitleHeightMethod = WebView.class
.getDeclaredMethod("getVisibleTitleHeight");
} catch(NoSuchMethodException e) {
Log.w(TAG,
"Could not retrieve native hidden getVisibleTitleHeight method");
}
}
/**
* The hidden method setEmbeddedTitleBar has been removed from Jelly Bean.
* This method replicates the functionality.
*
* @param v
*/
private void setEmbeddedTitleBarJellyBean(View v) {
if(mTitleBar == v) return;
if(mTitleBar != null) {
removeView(mTitleBar);
}
if(null != v) {
LayoutParams vParams = new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0);
TouchBlockView tbv = new TouchBlockView(getContext());
FrameLayout.LayoutParams tbvParams = new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
tbv.addView(v, tbvParams);
addView(tbv, vParams);
v = tbv;
}
mTitleBar = v;
}
@Override
public void onSetEmbeddedTitleBar(View title) { /* unused */ }
}

View File

@ -13,23 +13,19 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import com.nobu_games.android.view.web.TitleBarWebView;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Paint;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -42,7 +38,6 @@ import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebView; import android.webkit.WebView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
@ -174,16 +169,17 @@ public class ArticleFragment extends Fragment implements GestureDetector.OnDoubl
} }
} }
TitleBarWebView web = (TitleBarWebView)view.findViewById(R.id.content); TitleWebView web = (TitleWebView)view.findViewById(R.id.content);
if (web != null) { if (web != null) {
if (!m_activity.isPortrait() && m_activity.isSmallScreen()) { /* if (!m_activity.isPortrait() && m_activity.isSmallScreen()) {
View header = view.findViewById(R.id.article_header); View header = view.findViewById(R.id.article_header);
LinearLayout article = (LinearLayout)view.findViewById(R.id.article_fragment); LinearLayout article = (LinearLayout)view.findViewById(R.id.article_fragment);
article.removeView(header); article.removeView(header);
web.setEmbeddedTitleBarCompat(header); web.setEmbeddedTitleBarCompat(header);
} } */
web.setWebChromeClient(new WebChromeClient() { web.setWebChromeClient(new WebChromeClient() {
@Override @Override

View File

@ -0,0 +1,91 @@
package org.fox.ttrss;
// http://www.techques.com/question/1-9718245/Webview-in-Scrollview
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;
public class TitleWebView extends WebView{
public TitleWebView(Context context, AttributeSet attrs){
super(context, attrs);
}
private int titleHeight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// determine height of title bar
View title = getChildAt(0);
titleHeight = title==null ? 0 : title.getMeasuredHeight();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev){
return true; // don't pass our touch events to children (title bar), we send these in dispatchTouchEvent
}
private boolean touchInTitleBar;
@Override
public boolean dispatchTouchEvent(MotionEvent me){
boolean wasInTitle = false;
switch(me.getActionMasked()){
case MotionEvent.ACTION_DOWN:
touchInTitleBar = (me.getY() <= visibleTitleHeight());
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
wasInTitle = touchInTitleBar;
touchInTitleBar = false;
break;
}
if(touchInTitleBar || wasInTitle) {
View title = getChildAt(0);
if(title!=null) {
// this touch belongs to title bar, dispatch it here
me.offsetLocation(0, getScrollY());
return title.dispatchTouchEvent(me);
}
}
// this is our touch, offset and process
me.offsetLocation(0, -titleHeight);
return super.dispatchTouchEvent(me);
}
/**
* @return visible height of title (may return negative values)
*/
private int visibleTitleHeight(){
return titleHeight-getScrollY();
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt){
super.onScrollChanged(l, t, oldl, oldt);
View title = getChildAt(0);
if(title!=null) // undo horizontal scroll, so that title scrolls only vertically
title.offsetLeftAndRight(l - title.getLeft());
}
@Override
protected void onDraw(Canvas c){
c.save();
int tH = visibleTitleHeight();
if(tH>0) {
// clip so that it doesn't clear background under title bar
int sx = getScrollX(), sy = getScrollY();
c.clipRect(sx, sy+tH, sx+getWidth(), sy+getHeight());
}
c.translate(0, titleHeight);
super.onDraw(c);
c.restore();
}
}