htmlをネイティブコンポーネントに変える

良い一日 surfingbirdのモバイル開発者である私たちは、モバイルアプリケーション(Android、IOS)の開発プロセスで直面する困難、およびそれらの解決方法に関する短いシリーズの記事を作成することにしました。 ウェブビューの問題に専念することを決めた最初の投稿。 この問題を根本的に解決したことをすぐに予約します。より明確にするために、私たちが何をしているかについていくつかの言葉を話す必要があります。 さまざまなソースからコンテンツを集約し(元の記事を解析)、重要な部分(コンテンツ)を選択し、ユーザーの評価と複雑なアルゴリズムに基づいて、エンドユーザーに推奨します。もちろん、より便利な形式で表示するだけです。



モバイルアプリケーションでは、レイアウト要素や迷惑なポップアップからページを消去するだけでなく、モバイルデバイスでの使用に合わせてコンテンツを最適化するよう努めています。



しかし、webviewを使用してコンテンツを表示するとき、多くの困難に直面しました。 このコンポーネントはカスタマイズが難しく、非常に重く、バグがあります。 ウェブビューをまったく見たくないということに気づいた日がやってきました。 しかし、コンテンツがhtmlで私たちに与えられていることを考えると、それを取り除くために-それほど単純ではありませんでした。 そのため、htmlをネイティブコンポーネントに変えることにしました。



画像



コード例に移る前に、原理を簡単に説明しようとします。

  1. スタイルとJavaScriptからHTMLを削除します
  2. 参照ポイントとして、画像とiframeへのリンクを使用します
  3. 画像リンクの前後にあるのは、textviewを使用してレンダリングされるテキストのみです
  4. 直接画像-imageviewを使用してレンダリング
  5. Iframeの場合、コンテンツとビデオを分析し、クリック可能な画像としてビデオ上でレンダリングし、残りをリンクとしてレンダリングします。極端な場合は、webviewコンテナー(soundcloudのオーディオリンクなど)に挿入します
  6. 結果のコンポーネントの配列はリストビューとアダプターに配置されます(実際には既にrecyclerViewにありますが、これを書いている時点ではリストビューでした)




まず、javascriptとcssの形式のジャンクのHTMLをクリアする必要があります。 これらの目的のために、 HtmlCleanerライブラリーを使用しました 。 同時に、コンテンツに表示されるすべての画像の配列を作成します(後で必要になります)。



final ArrayList<Link> links = new ArrayList<Link>(); 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; } });
      
      







ここでは、最初にimgタグをimgs ^ _ ^に置き換えます。これにより、textviewが画像をレンダリングしようとしないようにし、次に、画像へのすべてのリンクを見つけてimageviewに置き換えます。



画像をネイティブで表示することにしたので、同時に画面の1/3以上の平均的な画像がスマートフォンのフルスクリーンになり、小さな画像が大きくなり、非常に小さなものを完全に無視できるように、それらを増やすことは悪くありません(方法ルールはソーシャルネットワークへのリンクのアイコンです):



 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<displayWidth/3) { //  2   scaleFactor = 2; } if (iHeight>=4096 || iWidth>=4096 || src.endsWith("gif")) { type = "iframe"; } return new Link(type, src, iWidth*scaleFactor, iHeight*scaleFactor,""); } return null; }
      
      







実際、作業の半分はすでに完了しています。 画像へのリンクの配列を調べ、画像の前のコンテンツを見つけてテキストビューに貼り付けてから、画像を挿入します。

これを行うために、実際のコンテンツ自体を配置するArrayListを作成し、そのタイプ(テキスト、画像、iframe)を示します。



ある種の擬似コード:



  private ArrayList<Link> data = new ArrayList<Link>();; for(int i=0;i<links.size();i++) { final Link link = links.get(i); if (link.type.equals("txt")) continue; int pos = html.indexOf(link.src); String abzats = ""; if (pos>0) { 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));
      
      







この場所には、コンテンツがタイプに分類された豪華な配列があります。 残っているのは、レンダリングすることだけです。 また、配列のレンダリングでは、通常のリストビュー+アダプターよりも美しいものを見つけるのは困難です。

アダプターのgetViewコードは次のようになります。



 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; }
      
      







したがって、テキストはtextviewを使用してhtmlとしてレンダリングされ、写真は通常の写真に変わりますが、デバイスの解像度に最適化されます。 iframeには痛みのみが残っています。 そのコンテンツを分析し、たとえばyoutubeへのリンクの場合、YouTubeアプリケーションを開くアイコンをクリックして、ビデオプレースホルダー付きの画像を生成します。 一般に、すべてはすでに非常に簡単です:



  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;
      
      







職場でのアプリケーションを示す短いビデオ撮影しましたが、アプリケーションをダウンロードして自分試してみることをお勧めします



おそらく、この方法は誰かにとってはやや基本的に思えるかもしれませんが、最終結果には満足しています。 ページの読み込みが速くなり、ネイティブで統一感があり、どのデバイスでも読みやすくなりました。 さらに、多くの興味深い機能が開き、ネイティブの写真プレビュー、フォント設定、ネイティブアプリケーションでビデオを開くことができます。もちろん、さまざまなバージョンや多くの場合、奇妙なWebビューの動作に問題はありません。



All Articles