HTML book-like pagination

后端 未结 10 1332
清歌不尽
清歌不尽 2020-11-27 09:29

How can I split the content of a HTML file in screen-sized chunks to \"paginate\" it in a WebKit browser?

Each \"page\" should show a complete amount of text. This

相关标签:
10条回答
  • 2020-11-27 09:54

    I was able to improve Nacho's solution to get horizontal swipe paging effect with WebView. You can find solution here and example code.

    Edit:

    Solution code.

    MainActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }
        wv = (HorizontalWebView) findViewById(R.id.web_view);
        wv.getSettings().setJavaScriptEnabled(true);
        wv.setWebViewClient(new WebViewClient() {
    
            public void onPageFinished(WebView view, String url) {
                injectJavascript();
            }
        });
    
        wv.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                int pageCount = Integer.parseInt(message);
                wv.setPageCount(pageCount);
                result.confirm();
                return true;
            }
        });
        wv.loadUrl("file:///android_asset/ch03.html");   // now it will not fail here
    }
    
    private void injectJavascript() {
        String js = "function initialize(){\n" +
                "    var d = document.getElementsByTagName('body')[0];\n" +
                "    var ourH = window.innerHeight;\n" +
                "    var ourW = window.innerWidth;\n" +
                "    var fullH = d.offsetHeight;\n" +
                "    var pageCount = Math.floor(fullH/ourH)+1;\n" +
                "    var currentPage = 0;\n" +
                "    var newW = pageCount*ourW;\n" +
                "    d.style.height = ourH+'px';\n" +
                "    d.style.width = newW+'px';\n" +
                "    d.style.margin = 0;\n" +
                "    d.style.webkitColumnCount = pageCount;\n" +
                "    return pageCount;\n" +
                "}";
        wv.loadUrl("javascript:" + js);
        wv.loadUrl("javascript:alert(initialize())");
    }
    

    In my WebChromeClient's onJsAlert get the number of horizontal pages which i pass to the custom HorizontalWebView to implement paging effect

    HorizontalWebView.java

    public class HorizontalWebView extends WebView {
        private float x1 = -1;
        private int pageCount = 0;
    
        public HorizontalWebView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    x1 = event.getX();
                    break;
                case MotionEvent.ACTION_UP:
                    float x2 = event.getX();
                    float deltaX = x2 - x1;
                    if (Math.abs(deltaX) > 100) {
                        // Left to Right swipe action
                        if (x2 > x1) {
                            turnPageLeft();
                            return true;
                        }
    
                        // Right to left swipe action
                        else {
                            turnPageRight();
                            return true;
                        }
    
                    }
                    break;
            }
            return true;
        }
    
        private int current_x = 0;
    
        private void turnPageLeft() {
            if (getCurrentPage() > 0) {
                int scrollX = getPrevPagePosition();
                loadAnimation(scrollX);
                current_x = scrollX;
                scrollTo(scrollX, 0);
            }
        }
    
        private int getPrevPagePosition() {
            int prevPage = getCurrentPage() - 1;
            return (int) Math.ceil(prevPage * this.getMeasuredWidth());
        }
    
        private void turnPageRight() {
            if (getCurrentPage() < pageCount - 1) {
                int scrollX = getNextPagePosition();
                loadAnimation(scrollX);
                current_x = scrollX;
                scrollTo(scrollX, 0);
            }
        }
    
        private void loadAnimation(int scrollX) {
            ObjectAnimator anim = ObjectAnimator.ofInt(this, "scrollX",
                    current_x, scrollX);
            anim.setDuration(500);
            anim.setInterpolator(new LinearInterpolator());
            anim.start();
        }
    
        private int getNextPagePosition() {
            int nextPage = getCurrentPage() + 1;
            return (int) Math.ceil(nextPage * this.getMeasuredWidth());
        }
    
        public int getCurrentPage() {
            return (int) (Math.ceil((double) getScrollX() / this.getMeasuredWidth()));
        }
    
        public void setPageCount(int pageCount) {
            this.pageCount = pageCount;
        }
    }
    
    0 讨论(0)
  • 2020-11-27 10:01

    I recently attempted something similar to this and added some CSS styling to change the layout to horizontal instead of vertical. This gave me the desired effect without having to modify the content of the Epub in any way.

    This code should work.

    mWebView.setWebViewClient(new WebViewClient() {
        public void onPageFinished(WebView view, String url) {
    
            // Column Count is just the number of 'screens' of text. Add one for partial 'screens'
            int columnCount = Math.floor(view.getHeight() / view.getWidth())+1;
    
            // Must be expressed as a percentage. If not set then the WebView will not stretch to give the desired effect.
            int columnWidth = columnCount * 100;
    
            String js = "var d = document.getElementsByTagName('body')[0];" + 
                "d.style.WebkitColumnCount=" + columnCount + ";" + 
                "d.style.WebkitColumnWidth='" + columnWidth + "%';";
            mWebView.loadUrl("javascript:(function(){" + js + "})()");
        }
    });
    
    mWebView.loadUrl("file:///android_asset/chapter.xml");
    

    So, basically you're injecting JavaScript to change the styling of the body element after the chapter has been loaded (very important). The only downfall to this approach is when you have images in the content the calculated column count goes askew. It shouldn't be too hard to fix though. My attempt was going to be injecting some JavaScript to add width and height attributes to all images in the DOM that don't have any.

    Hope it helps.

    -Dan

    0 讨论(0)
  • 2020-11-27 10:07

    I've had to code something like this too, and my (working) solution is this:

    You have to apply these lines to the webview...

        webView_.getSettings().setUseWideViewPort(true);
        webView_.getSettings().setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS);
    

    Also, you have to inject some javascript. I've had tons of problems with the differents scales of my activity and the content rendered in the webview, so my solution doesn't take any kind of value from "outside".

        webView_.setWebViewClient(new WebViewClient(){
    
                public void onPageFinished(WebView view, String url) {
                    injectJavascript();
                }
    
            });
    

    [...]

        public void injectJavascript() {
            String js = "javascript:function initialize() { " +
                    "var d = document.getElementsByTagName('body')[0];" +
                    "var ourH = window.innerHeight; " +
                    "var ourW = window.innerWidth; " + 
                    "var fullH = d.offsetHeight; " +
                    "var pageCount = Math.floor(fullH/ourH)+1;" +
                    "var currentPage = 0; " +
                    "var newW = pageCount*ourW; " +
                    "d.style.height = ourH+'px';" +
                    "d.style.width = newW+'px';" +
                    "d.style.webkitColumnGap = '2px'; " +
                    "d.style.margin = 0; " +
                    "d.style.webkitColumnCount = pageCount;" +
                    "}";
            webView_.loadUrl(js);
            webView_.loadUrl("javascript:initialize()");
        }
    

    Enjoy :)

    0 讨论(0)
  • 2020-11-27 10:08

    Maybe it would work to use XSL-FO. This seems heavy for a mobile device, and maybe it's overkill, but it should work, and you wouldn't have to implement the complexities of good pagination (e.g. how do you make sure that each screen doesn't cut text in half) yourself.

    The basic idea would be:

    • transform the XHTML (and other EPUB stuff) to XSL-FO using XSLT.
    • use an XSL-FO processor to render the XSL-FO into a paged format that you can display on the mobile device, such as PDF (can you display that?)

    I don't know whether there is an XSL-FO processor available for Android. You could try Apache FOP. RenderX (XSL-FO processor) has the advantage of having a paged-HTML output option, but again I don't know if it could run on Android.

    0 讨论(0)
  • 2020-11-27 10:09

    There is several ways this could be done. If every line is in its own element all you have to do is to check if one of it's edges goes outside of the view (either the browsers, or the "book page").

    If you want to know how many "pages" there is going to be in advance, just temporary move them into the view and get what line a page ends. This could potentially be slow because of that page reflow is needed for the browser to know where anything is.

    Otherwise I think that you could use the HTML5 canvas element to measure text and / or draw text.

    Some info on that here: https://developer.mozilla.org/en/Drawing_text_using_a_canvas http://uupaa-js-spinoff.googlecode.com/svn/trunk/uupaa-excanvas.js/demo/8_2_canvas_measureText.html

    0 讨论(0)
  • 2020-11-27 10:11

    Building on Dan's answer here is my solution for this problem, with which I was struggling myself until just now. (this JS works on iOS Webkit, no guarantees for android, but please let me know the results)

    var desiredHeight;
    var desiredWidth;
    var bodyID = document.getElementsByTagName('body')[0];
    totalHeight = bodyID.offsetHeight;
    pageCount = Math.floor(totalHeight/desiredHeight) + 1;
    bodyID.style.padding = 10; //(optional) prevents clipped letters around the edges
    bodyID.style.width = desiredWidth * pageCount;
    bodyID.style.height = desiredHeight;
    bodyID.style.WebkitColumnCount = pageCount;
    

    Hope this helps...

    0 讨论(0)
提交回复
热议问题