Verwandle HTML in native Komponenten

    Guten Tag! Wir, die mobilen Entwickler von surfingbird, haben uns entschlossen, eine kurze Artikelserie darüber zu schreiben, mit welchen Schwierigkeiten wir bei der Entwicklung mobiler Anwendungen (Android, iOS) konfrontiert sind und wie wir sie lösen. Den ersten Beitrag haben wir uns für Webview entschieden. Ich mache sofort einen Vorbehalt, dass wir dieses Problem radikal entschieden haben ... Um es klarer zu machen, müssen wir ein paar Worte darüber sagen, was wir tun. Wir aggregieren Inhalte aus verschiedenen Quellen (analysieren Originalartikel), wählen einen wesentlichen Teil (Inhalt) aus und empfehlen sie dem Endbenutzer basierend auf Benutzerbewertungen und komplexen Algorithmen und zeigen sie natürlich einfach in einer bequemeren Form an.

    In mobilen Anwendungen streben wir nicht nur danach, Seiten von Layoutelementen und lästigen Popups zu befreien, sondern auch Inhalte für den Verbrauch auf Mobilgeräten zu optimieren.

    Bei der Verwendung von Webview zur Anzeige von Inhalten stießen wir jedoch auf eine Reihe von Schwierigkeiten. Diese Komponente ist schwer anzupassen und ziemlich schwer und sogar, würde ich sagen, fehlerhaft. Der Tag kam, an dem wir realisierten, dass wir überhaupt keine Webansicht mehr sehen möchten. Aber es loszuwerden, da der Inhalt uns in HTML gegeben wird - war nicht so einfach. Aus diesem Grund haben wir uns entschlossen, HTML in native Komponenten umzuwandeln.

    Bild

    Ich werde versuchen, das Prinzip kurz zu beschreiben, bevor ich zu den Codebeispielen übergehe.
    1. Wir bereinigen HTML von Styles und Javascripts
    2. Als Referenz verwenden wir Links zu Bildern und Iframes
    3. Vor und zwischen den Bildverknüpfungen steht nur Text, der mit der Textansicht gerendert wird
    4. Direkt Bilder - Rendern mit ImageView
    5. Für Iframe analysieren wir den Inhalt und das Video, rendern es als anklickbare Bilder im Video und rendern den Rest als Links oder fügen es im Extremfall in den Webview-Container ein (z. B. Audio-Links mit Soundcloud).
    6. Wir haben das resultierende Array von Komponenten in die Listenansicht und den Adapter eingefügt (tatsächlich bereits in recyclerView, aber zum Zeitpunkt des Schreibens war dies eine Listenansicht).


    Zuallererst müssen Sie HTML von jeglichem Müll in Form von Javascript und CSS löschen. Zu diesem Zweck haben wir die HtmlCleaner- Bibliothek verwendet . Gleichzeitig erstellen wir ein Array aller Bilder, die im Inhalt erscheinen (wir werden es später brauchen):

        final ArrayList links = new ArrayList();
        HtmlCleaner mHtmlCleaner = new HtmlCleaner();
        CleanerTransformations transformations =
                new CleanerTransformations();
        TagTransformation tt = new TagTransformation("img", "imgs", true);
        transformations.addTransformation(tt);
        mHtmlCleaner.setTransformations(transformations);
        //clean
        html = mHtmlCleaner.getInnerHtml(mHtmlCleaner.clean(parsed_content));
        TagNode root = mHtmlCleaner.clean(html);
        root.traverse(new TagNodeVisitor() {
            @Override
            public boolean visit(TagNode tagNode, HtmlNode htmlNode) {
                if (htmlNode instanceof TagNode) {
                    TagNode tag = (TagNode) htmlNode;
                    String tagName = tag.getName();
                    if ("iframe".equals(tagName)) {
                        if (tag.getAttributeByName("src") != null) {
                            Link link = parseTag(tag, "iframe");
                            if (link != null) {
                                links.add(link);
                            }
                        }
                    }
                    if ("imgs".equals(tagName)) {
                        String src = tag.getAttributeByName("src");
                        //ico
                        if (src != null && !src.endsWith("/") && !src.toLowerCase().endsWith("ico")) {
                            Link link = parseTag(tag, "img");
                            if (link != null) {
                                links.add(link);
                            }
                        }
                    }
                }
                return true;
            }
        });
    


    Hier ersetzen wir die img-Tags erstens durch imgs ^ _ ^, damit die Textansicht nicht versucht ist, Bilder zu rendern, und zweitens, um dann alle Links zu Bildern zu finden und sie durch imageview zu ersetzen.

    Da wir uns entschieden haben, die Bilder nativ anzuzeigen, wäre es nicht schlecht, sie gleichzeitig zu vergrößern, damit durchschnittliche Bilder, beispielsweise mehr als 1/3 des Bildschirms, den gesamten Bildschirm des Smartphones ausfüllen, kleine Bilder größer werden und sehr kleine Bilder völlig vernachlässigt werden können (wie die Regel sind die Symbole der Links zu sozialen Netzwerken):

    public Link parseTag(TagNode tag,String type) {
            final String src = tag.getAttributeByName("src");
            final String width = tag.getAttributeByName("width");
            final String height = tag.getAttributeByName("height");
            int iWidth=0, iHeight=0;
            try {
                iWidth = Integer.parseInt(width.split("\\.")[0]);
                iHeight = Integer.parseInt(height.split("\\.")[0]);
            }
            catch (Exception e) {}
            //если картинка больше 1/3 экрана - тянем пропорционально
            if (iWidth>((displayWidth*1)/3) && iHeight>0) {
                iHeight = (displayWidth * iHeight)/iWidth;
                iWidth = displayWidth;
            }
            //выкидываем мелкие пиписьки
            if (iWidth>45 && iHeight>45) {
                int scaleFactor = 1;
                if (iWidth=4096 || iWidth>=4096 || src.endsWith("gif")) {
                    type = "iframe";
                }
                return new Link(type, src, iWidth*scaleFactor, iHeight*scaleFactor,"");
            }
            return null;
        }
    


    Eigentlich ist die Hälfte der Arbeit schon erledigt. Jetzt müssen Sie noch die Verknüpfungen zu den Bildern durchgehen, den Inhalt vor dem Bild suchen und in eine Textansicht einfügen und dann das Bild einfügen.
    Zu diesem Zweck haben wir eine ArrayList erstellt, in die wir den eigentlichen Inhalt einfügen und dessen Typ (Text, Bild, Iframe) angeben.

    Eine Art Pseudocode:

        private ArrayList data = new ArrayList();;
        for(int i=0;i0) {
                abzats = html.substring(0, pos);
                int closeTag = html.indexOf(">",pos)+1;
                if (closeTag>0) {
                    html = html.substring(closeTag);
                }
                if (!TextUtils.equals("", abzats)) {
                    data.add(new Link("txt","",0,0,abzats));
                }
            }
            //add text
            if (link.type.equals("img")) {
                //add image
                data.add(link);
            }
            //add iframe
            if (link.type.equals("iframe")) {
                data.add(link);
            }
        }
        data.add(new Link("txt","",0,0,html));
    


    An dieser Stelle haben wir ein großartiges Array mit Inhalten, die in Typen unterteilt sind. Alles was bleibt ist es zu rendern. Und für das Rendern von Arrays gibt es kaum etwas Schöneres als einen normalen Listview + Adapter: Der
    getView-Code im Adapter sieht ungefähr so ​​aus:

    if (link.type.equals("txt")) {
      //текст
               return getTextView(activity, link.txt);
    }
    if (link.type.equals("img")) {
     // картинка
    }
    ...
    //где, textview 
    public TextView getTextView(Context context,String txt){
            TextView textView = new TextView(activity);
            textView.setMovementMethod(LinkMovementMethod.getInstance());
            textView.setText(Html.fromHtml(txt));
            textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,fontSize);
            textView.setPadding(UtilsScreen.dpToPx(8),0,UtilsScreen.dpToPx(8),0);
            textView.setAutoLinkMask(Linkify.ALL);
            textView.setLineSpacing(0, 1.4f);
            ColorStateList cl = null;
            try {
                XmlResourceParser xpp = context.getResources().getXml(R.xml.textview_link_color_selector);
                cl = ColorStateList.createFromXml(context.getResources(), xpp);
                textView.setLinkTextColor(cl);
            } catch (Exception e) {
                textView.setLinkTextColor(Color.parseColor("#6fb304"));
            }
            return textView;
        }
    


    Der Text wird also in der Textansicht als HTML gerendert, die Bilder werden zu gewöhnlichen Bildern, aber für die Auflösung des Geräts optimiert. Es gibt nur noch Schmerzen mit dem Iframe. Wir analysieren deren Inhalte und erzeugen, wenn es sich beispielsweise um einen Link zu YouTube handelt, ein Bild mit einem Video-Platzhalter, indem wir darauf klicken, um die YouTube-Anwendung zu öffnen. Im Allgemeinen ist alles schon ganz einfach:

        String youtubeVideo = "";
        if (link.src.contains("lj-toys") && link.src.contains("youtube") && link.src.contains("vid=")) {
            try {
                youtubeVideo = link.src.substring(link.src.indexOf("vid=") + 4, link.src.indexOf("&", link.src.indexOf("vid=") + 4));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //http://www.youtube.com/embed/ZSPyC6Uv9xw
        if (link.src.contains("youtube") && link.src.contains("embed/")) {
            try {
                youtubeVideo = link.src.substring(link.src.indexOf("embed/") + 6);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (!youtubeVideo.equals("")) {
            //new RelativeLayout
            RelativeLayout relativeLayout = new RelativeLayout(activity);
            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
            ImageView imageView = new ImageView(activity);
            imageView.setLayoutParams(layoutParams);
            relativeLayout.addView(imageView);
            imageView.setBackgroundColor(Color.parseColor("#f8f8f8"));
            if (link.width>0 && link.height>0) {
                aq.id(imageView).width(link.width, false).height(link.height, false);
            }
            String youtubeVideoImage = youtubeVideo;
            if (youtubeVideoImage.contains("?")) {
                //params
                youtubeVideoImage = youtubeVideoImage.substring(0, youtubeVideoImage.indexOf("?"));
            }
            if (link.width>0) {
                aq.id(imageView).image("http://img.youtube.com/vi/" + youtubeVideoImage + "/0.jpg", true, false, link.width, 0, null, AQuery.FADE_IN_NETWORK);
            }
            else {
                aq.id(imageView).image("http://img.youtube.com/vi/" + youtubeVideoImage + "/0.jpg");
            }
            ImageView imageViewPlayBtn = new ImageView(activity);
            relativeLayout.addView(imageViewPlayBtn);
            RelativeLayout.LayoutParams playBtnParams = new RelativeLayout.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            playBtnParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
            imageViewPlayBtn.setLayoutParams(playBtnParams);
            aq.id(imageViewPlayBtn).image(R.drawable.play_youtube);
            final String videoId = youtubeVideo;
            aq.id(relativeLayout).clickable(true).clicked(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("vnd.youtube:" + videoId));
                        intent.putExtra("VIDEO_ID", videoId);
                        activity.startActivity(intent);
                    } catch (Exception e) {
                        activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.youtube.com/watch?v=" + videoId)));
                    }
                }
            });
            return relativeLayout;
    


    Wir haben ein kurzes Video gedreht , in dem die Anwendung bei der Arbeit demonstriert wird. Es ist jedoch besser, die Anwendung herunterzuladen und selbst auszuprobieren .

    Vielleicht scheint diese Methode für jemanden etwas kardinal zu sein, aber wir sind mit dem Endergebnis zufrieden. Die Seiten wurden schneller geladen, sahen einheitlich aus und waren auf jedem Gerät einfach zu lesen. Außerdem eröffnen sich eine Reihe interessanter Funktionen, eine native Fotovorschau, Schrifteinstellungen, das Öffnen eines Videos in einer nativen Anwendung, und natürlich gibt es keine Probleme mit verschiedenen Versionen und oftmals komischem Webview-Verhalten.

    Jetzt auch beliebt: