无法在 API 级别 29 上的 wordpress 应用程序的 webview 中完全加载 wordpress post 内容。但它在 API 级别 26 上工作正常
Not able to load wordpress post content fully in webview in wordpress app on API Level 29. But it works fine on API Level 26
我正在开发 WordPress 应用程序,它从 Wordpress 站点获取 post 并将其加载到 webview 中。我正在使用 "WP REST API"(在 JSON 中获取 post 数据)和改造库。在 API 26
应用程序运行良好 WordPress post 在迁移到 androidx API 29
后完全加载 post 内容在一些 post & 在中仅加载 1-3 行另一个 post 10 行。这可能是什么原因?,内容没有完全加载。迁移到 androidX 后 post 未加载我收到错误 ERR_CLEARTEXT_NOT_PERMITTED
所以我在 post 加载但未完全加载后添加 android:usesCleartextTraffic="true"
。
这是我的 activity post 我选择的 Wordpress 博客 post 加载的地方
activity_post_details.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/post_img"
android:layout_width="match_parent"
android:layout_height="@dimen/margin_250dp"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@color/white"
app:layout_collapseMode="parallax" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@drawable/bg_gradient_toolbar"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton
android:id="@+id/imgBtnShare"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:scaleType="centerInside"
android:src="@drawable/ic_share_white" />
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/content_post_details" />
<include layout="@layout/content_comments" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_new_comment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/activity_vertical_margin"
android:src="@drawable/ic_fab"
android:visibility="gone" />
<include layout="@layout/view_common_loader" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
这是我的内容布局post
content_post_details.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/lyt_post_details"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:visibility="gone">
<LinearLayout
android:id="@+id/li_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_8dp"
android:layout_marginTop="@dimen/margin_8dp"
android:orientation="horizontal"
android:weightSum="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_20dp"
android:layout_weight="0.5"
android:gravity="center_vertical"
android:padding="@dimen/margin_8dp">
<ImageView
android:id="@+id/author_image"
android:layout_width="@dimen/margin_30dp"
android:layout_height="@dimen/margin_30dp"
android:background="@drawable/ic_author" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_20dp"
android:layout_weight="0.5"
android:gravity="center_vertical"
android:padding="@dimen/margin_8dp">
<ImageView
android:id="@+id/post_date_image"
android:layout_width="@dimen/margin_30dp"
android:layout_height="@dimen/margin_30dp"
android:layout_marginLeft="@dimen/margin_8dp"
android:layout_toRightOf="@id/post_author"
android:background="@drawable/ic_calendar" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/viewDivider"
android:layout_width="match_parent"
android:layout_height="0.7dp"
android:layout_below="@id/li_layout"
android:layout_marginLeft="@dimen/margin_15dp"
android:layout_marginRight="@dimen/margin_15dp"
android:background="@color/toolbar_boarder" />
<TextView
android:id="@+id/title_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/viewDivider"
android:layout_margin="@dimen/margin_8dp"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold"
tools:text="This is sample text do yoy know that it supports multi-line" />
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title_text"
android:layout_margin="@dimen/margin_8dp"
android:paddingBottom="@dimen/margin_8dp" />
</RelativeLayout>
这是我的 java 文件,它处理 Post 详细信息。
PostdetailsActivity.java
package ak.wp.meto.activity;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.fragment.app.FragmentManager;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.text.Html;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import ak.wp.meto.R;
import ak.wp.meto.adapters.CommentsAdapter;
import ak.wp.meto.api.http.ApiUtils;
import ak.wp.meto.api.models.posts.post.CommentsAndReplies;
import ak.wp.meto.api.models.posts.post.PostDetails;
import ak.wp.meto.api.params.HttpParams;
import ak.wp.meto.data.constant.AppConstant;
import ak.wp.meto.data.sqlite.FavouriteDbController;
import ak.wp.meto.fragment.WriteACommentFragment;
import ak.wp.meto.listeners.ListItemClickListener;
import ak.wp.meto.models.FavouriteModel;
import ak.wp.meto.utility.ActivityUtils;
import ak.wp.meto.utility.AppUtils;
import ak.wp.meto.utility.TtsEngine;
import ak.wp.meto.webengine.WebEngine;
import ak.wp.meto.webengine.WebListener;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class PostDetailsActivity extends BaseActivity implements WriteACommentFragment.OnCompleteListener {
// Variables
private Activity mActivity;
private Context mContext;
// init views
private RelativeLayout lytPostDetailsView, lytCommentDetails;
private int clickedPostId;
private ImageView imgPost;
private TextView tvPostTitle, tvPostAuthor, tvPostDate, tvCommnentList;
private WebView webView;
private FloatingActionButton fabWriteAComment;
private ImageButton imgBtnSpeaker, imgBtnFav, imgBtnShare;
private PostDetails model = null;
// Favourites view
private List<FavouriteModel> favouriteList;
private FavouriteDbController favouriteDbController;
private boolean isFavourite = false;
// Comments view
private List<CommentsAndReplies> commentList;
private List<CommentsAndReplies> zeroParentComments;
List<CommentsAndReplies> onlyThreeComments;
private int mPerPage = 5;
private RecyclerView rvComments;
private CommentsAdapter commentsAdapter = null;
// Text to speech
private TtsEngine ttsEngine;
private boolean isTtsPlaying = false;
private String ttsText;
private WebEngine webEngine;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initVar();
initView();
initFunctionality();
initListener();
}
private void initVar() {
mActivity = PostDetailsActivity.this;
mContext = mActivity.getApplicationContext();
favouriteList = new ArrayList<>();
commentList = new ArrayList<>();
zeroParentComments = new ArrayList<>();
onlyThreeComments = new ArrayList<>();
Intent intent = getIntent();
if (intent != null) {
clickedPostId = getIntent().getIntExtra(AppConstant.BUNDLE_KEY_POST_ID, 0);
}
}
private void initView() {
setContentView(R.layout.activity_post_details);
lytPostDetailsView = (RelativeLayout) findViewById(R.id.lyt_post_details);
lytCommentDetails = (RelativeLayout) findViewById(R.id.lyt_comment_list);
imgPost = (ImageView) findViewById(R.id.post_img);
tvPostTitle = (TextView) findViewById(R.id.title_text);
tvPostAuthor = (TextView) findViewById(R.id.post_author);
tvPostDate = (TextView) findViewById(R.id.date_text);
imgBtnSpeaker = (ImageButton) findViewById(R.id.imgBtnSpeaker);
imgBtnFav = (ImageButton) findViewById(R.id.imgBtnFavourite);
imgBtnShare = (ImageButton) findViewById(R.id.imgBtnShare);
initWebEngine();
tvCommnentList = (TextView) findViewById(R.id.comment_count);
fabWriteAComment = (FloatingActionButton) findViewById(R.id.fab_new_comment);
rvComments = (RecyclerView) findViewById(R.id.rvComments);
rvComments.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
initLoader();
initToolbar();
enableBackButton();
}
public void initWebEngine() {
webView = (WebView) findViewById(R.id.web_view);
webEngine = new WebEngine(webView, mActivity);
webEngine.initWebView();
webEngine.initListeners(new WebListener() {
@Override
public void onStart() {
initLoader();
}
@Override
public void onLoaded() {
hideLoader();
}
@Override
public void onProgress(int progress) {
}
@Override
public void onNetworkError() {
showEmptyView();
}
@Override
public void onPageTitle(String title) {
}
});
}
private void initFunctionality() {
favouriteDbController = new FavouriteDbController(mContext);
favouriteList.addAll(favouriteDbController.getAllData());
for (int i = 0; i < favouriteList.size(); i++) {
if (favouriteList.get(i).getPostId() == clickedPostId) {
isFavourite = true;
break;
}
}
ttsEngine = new TtsEngine(mActivity);
commentsAdapter = new CommentsAdapter(mActivity, (ArrayList) commentList, (ArrayList) onlyThreeComments);
rvComments.setAdapter(commentsAdapter);
showLoader();
loadPostDetails();
}
public void setFavImage() {
if (isFavourite) {
imgBtnFav.setImageDrawable(ContextCompat.getDrawable(mActivity, R.drawable.ic_book));
} else {
imgBtnFav.setImageDrawable(ContextCompat.getDrawable(mActivity, R.drawable.ic_un_book));
}
}
public void initListener() {
imgBtnSpeaker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (model != null) {
toggleTtsPlay();
}
}
});
imgBtnFav.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (model != null) {
isFavourite = !isFavourite;
if (isFavourite) {
String imgUrl = null;
if (model.getEmbedded().getWpFeaturedMedias().size() >= 1) {
imgUrl = model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails().getSizes().getFullSize().getSourceUrl();
}
favouriteDbController.insertData(
model.getID().intValue(),
imgUrl,
model.getTitle().getRendered(),
model.getOldDate(),
model.getEmbedded().getWpTerms().get(0).get(0).getName()
);
} else {
favouriteDbController.deleteFav(clickedPostId);
}
setFavImage();
}
}
});
imgBtnShare.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (model != null) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, model.getPageUrl());
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));
}
}
});
commentsAdapter.setItemClickListener(new ListItemClickListener() {
@Override
public void onItemClick(int position, View view) {
int id = view.getId();
CommentsAndReplies clickedComment = zeroParentComments.get(position);
switch (id) {
case R.id.list_item:
ActivityUtils.getInstance().invokeCommentDetails(mActivity, CommentDetailsActivity.class, (ArrayList) commentList, clickedPostId, clickedComment, false, false);
break;
case R.id.reply_text:
ActivityUtils.getInstance().invokeCommentDetails(mActivity, CommentDetailsActivity.class, (ArrayList) commentList, clickedPostId, clickedComment, true, false);
break;
default:
break;
}
}
});
tvCommnentList.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityUtils.getInstance().invokeCommentList(mActivity,
CommentListActivity.class,
(ArrayList) commentList,
(ArrayList) zeroParentComments,
clickedPostId,
false);
}
});
fabWriteAComment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager manager = getSupportFragmentManager();
WriteACommentFragment dialog = WriteACommentFragment.newInstance(clickedPostId, AppConstant.THIS_IS_COMMENT);
dialog.show(manager, AppConstant.BUNDLE_KEY_DIALOG_FRAGMENT);
}
});
}
public void loadPostDetails() {
ApiUtils.getApiInterface().getPostDetails(clickedPostId).enqueue(new Callback<PostDetails>() {
@Override
public void onResponse(Call<PostDetails> call, Response<PostDetails> response) {
if (response.isSuccessful()) {
// bind data
model = response.body();
PostDetails m = model;
// visible parent view
lytPostDetailsView.setVisibility(View.VISIBLE);
// visible comments button
fabWriteAComment.setVisibility(View.VISIBLE);
loadCommentsAndReplies(model.getLinks().getRepliesList().get(0).getHref());
setFavImage();
tvPostTitle.setText(Html.fromHtml(model.getTitle().getRendered()));
String imgUrl = null;
if (model.getEmbedded().getWpFeaturedMedias().size() > 0) {
if (model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails() != null) {
if (model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails().getSizes().getFullSize().getSourceUrl() != null) {
imgUrl = model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails().getSizes().getFullSize().getSourceUrl();
}
}
}
if (imgUrl != null) {
Glide.with(getApplicationContext())
.load(imgUrl)
.into(imgPost);
}
String author = null;
if (model.getEmbedded().getAuthors().size() >= 1) {
author = model.getEmbedded().getAuthors().get(0).getName();
}
if (author == null) {
author = getString(R.string.admin);
}
tvPostAuthor.setText(Html.fromHtml(author));
String oldDate = model.getOldDate();
String newDate = AppUtils.getFormattedDate(oldDate);
if (newDate != null) {
tvPostDate.setText(Html.fromHtml(newDate));
}
String contentText = model.getContent().getRendered();
ttsText = new StringBuilder(Html.fromHtml(model.getTitle().getRendered())).append(AppConstant.DOT).append(Html.fromHtml(model.getContent().getRendered())).toString();
//webView.loadData(contentText, "text/html", "UTF-8");
contentText = new StringBuilder().append(AppConstant.CSS_PROPERTIES).append(contentText).toString();
webEngine.loadHtml(contentText);
} else {
showEmptyView();
}
}
@Override
public void onFailure(Call<PostDetails> call, Throwable t) {
t.printStackTrace();
// hide common loader
hideLoader();
// show empty view
showEmptyView();
}
});
}
public void loadCommentsAndReplies(final String commentsAndRepliesLink) {
ApiUtils.getApiInterface().getCommentsAndReplies(commentsAndRepliesLink, mPerPage).enqueue(new Callback<List<CommentsAndReplies>>() {
@Override
public void onResponse(Call<List<CommentsAndReplies>> call, Response<List<CommentsAndReplies>> response) {
if (response.isSuccessful()) {
int totalItems = Integer.parseInt(response.headers().get(HttpParams.HEADER_TOTAL_ITEM));
int totalPages = Integer.parseInt(response.headers().get(HttpParams.HEADER_TOTAL_PAGE));
if (totalPages > 1) {
mPerPage = mPerPage * totalPages;
loadCommentsAndReplies(commentsAndRepliesLink);
} else {
if (!commentList.isEmpty() || !zeroParentComments.isEmpty() || !onlyThreeComments.isEmpty()) {
commentList.clear();
zeroParentComments.clear();
onlyThreeComments.clear();
}
commentList.addAll(response.body());
lytCommentDetails.setVisibility(View.VISIBLE);
if (commentList.size() > 0) {
for (CommentsAndReplies commentsAndReplies : commentList) {
if (commentsAndReplies.getParent().intValue() == 0) {
zeroParentComments.add(commentsAndReplies);
}
}
if (zeroParentComments.size() >= 3) {
for (int i = 0; i < 3; i++) {
onlyThreeComments.add(zeroParentComments.get(i));
}
} else {
for (CommentsAndReplies commentsAndReplies : zeroParentComments) {
onlyThreeComments.add(commentsAndReplies);
}
}
commentsAdapter.notifyDataSetChanged();
tvCommnentList.setText(String.format(getString(R.string.all_comment), commentList.size()));
tvCommnentList.setClickable(true);
} else {
tvCommnentList.setClickable(false);
}
}
}
}
@Override
public void onFailure(Call<List<CommentsAndReplies>> call, Throwable t) {
showEmptyView();
t.printStackTrace();
}
});
}
@Override
public void onComplete(Boolean isCommentSuccessful, CommentsAndReplies commentsAndReplies) {
if (isCommentSuccessful) {
loadCommentsAndReplies(model.getLinks().getRepliesList().get(0).getHref());
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}
if (requestCode == AppConstant.REQUEST_CODE_COMMENT) {
if (data == null) {
return;
}
boolean isCommentSuccessful = CommentListActivity.wasCommentSuccessful(data);
if (isCommentSuccessful) {
loadCommentsAndReplies(model.getLinks().getRepliesList().get(0).getHref());
}
}
}
}
还找到build.gradle
build.gradle(应用级别)
apply plugin: 'com.android.application'
android {
lintOptions{
checkReleaseBuilds false
abortOnError false
}
compileSdkVersion 29
buildToolsVersion "28.0.3"
defaultConfig {
applicationId "ak.wp.meto"
minSdkVersion 17
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'androidx.appcompat:appcompat:1.0.0'
compile 'com.google.android.material:material:1.0.0'
compile 'androidx.constraintlayout:constraintlayout:1.1.3'
compile 'androidx.cardview:cardview:1.0.0'
//retrofit, gson
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
//glide
compile 'com.github.bumptech.glide:glide:4.3.1'
//circular imageview
compile 'de.hdodenhof:circleimageview:2.1.0'
}
apply plugin: 'com.google.gms.google-services'
我对这个问题的临时解决方案是从我的站点中删除了 postrophe、分号、引号和连字符。
现在一切正常 post 正在完全加载。
在 JSON 中,数据连字符被编码为 –
在 webview 中,在剩余的行消失后仅显示“&”。所以我设法删除了特殊字符。
用那个特殊字符替换你的字符串String contentText = model.getContent().replace("’","'");
我正在开发 WordPress 应用程序,它从 Wordpress 站点获取 post 并将其加载到 webview 中。我正在使用 "WP REST API"(在 JSON 中获取 post 数据)和改造库。在 API 26
应用程序运行良好 WordPress post 在迁移到 androidx API 29
后完全加载 post 内容在一些 post & 在中仅加载 1-3 行另一个 post 10 行。这可能是什么原因?,内容没有完全加载。迁移到 androidX 后 post 未加载我收到错误 ERR_CLEARTEXT_NOT_PERMITTED
所以我在 post 加载但未完全加载后添加 android:usesCleartextTraffic="true"
。
这是我的 activity post 我选择的 Wordpress 博客 post 加载的地方
activity_post_details.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/post_img"
android:layout_width="match_parent"
android:layout_height="@dimen/margin_250dp"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@color/white"
app:layout_collapseMode="parallax" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@drawable/bg_gradient_toolbar"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton
android:id="@+id/imgBtnShare"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:scaleType="centerInside"
android:src="@drawable/ic_share_white" />
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/content_post_details" />
<include layout="@layout/content_comments" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_new_comment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/activity_vertical_margin"
android:src="@drawable/ic_fab"
android:visibility="gone" />
<include layout="@layout/view_common_loader" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
这是我的内容布局post content_post_details.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/lyt_post_details"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:visibility="gone">
<LinearLayout
android:id="@+id/li_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_8dp"
android:layout_marginTop="@dimen/margin_8dp"
android:orientation="horizontal"
android:weightSum="1">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_20dp"
android:layout_weight="0.5"
android:gravity="center_vertical"
android:padding="@dimen/margin_8dp">
<ImageView
android:id="@+id/author_image"
android:layout_width="@dimen/margin_30dp"
android:layout_height="@dimen/margin_30dp"
android:background="@drawable/ic_author" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_20dp"
android:layout_weight="0.5"
android:gravity="center_vertical"
android:padding="@dimen/margin_8dp">
<ImageView
android:id="@+id/post_date_image"
android:layout_width="@dimen/margin_30dp"
android:layout_height="@dimen/margin_30dp"
android:layout_marginLeft="@dimen/margin_8dp"
android:layout_toRightOf="@id/post_author"
android:background="@drawable/ic_calendar" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/viewDivider"
android:layout_width="match_parent"
android:layout_height="0.7dp"
android:layout_below="@id/li_layout"
android:layout_marginLeft="@dimen/margin_15dp"
android:layout_marginRight="@dimen/margin_15dp"
android:background="@color/toolbar_boarder" />
<TextView
android:id="@+id/title_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/viewDivider"
android:layout_margin="@dimen/margin_8dp"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold"
tools:text="This is sample text do yoy know that it supports multi-line" />
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title_text"
android:layout_margin="@dimen/margin_8dp"
android:paddingBottom="@dimen/margin_8dp" />
</RelativeLayout>
这是我的 java 文件,它处理 Post 详细信息。
PostdetailsActivity.java
package ak.wp.meto.activity;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.fragment.app.FragmentManager;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.text.Html;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import ak.wp.meto.R;
import ak.wp.meto.adapters.CommentsAdapter;
import ak.wp.meto.api.http.ApiUtils;
import ak.wp.meto.api.models.posts.post.CommentsAndReplies;
import ak.wp.meto.api.models.posts.post.PostDetails;
import ak.wp.meto.api.params.HttpParams;
import ak.wp.meto.data.constant.AppConstant;
import ak.wp.meto.data.sqlite.FavouriteDbController;
import ak.wp.meto.fragment.WriteACommentFragment;
import ak.wp.meto.listeners.ListItemClickListener;
import ak.wp.meto.models.FavouriteModel;
import ak.wp.meto.utility.ActivityUtils;
import ak.wp.meto.utility.AppUtils;
import ak.wp.meto.utility.TtsEngine;
import ak.wp.meto.webengine.WebEngine;
import ak.wp.meto.webengine.WebListener;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class PostDetailsActivity extends BaseActivity implements WriteACommentFragment.OnCompleteListener {
// Variables
private Activity mActivity;
private Context mContext;
// init views
private RelativeLayout lytPostDetailsView, lytCommentDetails;
private int clickedPostId;
private ImageView imgPost;
private TextView tvPostTitle, tvPostAuthor, tvPostDate, tvCommnentList;
private WebView webView;
private FloatingActionButton fabWriteAComment;
private ImageButton imgBtnSpeaker, imgBtnFav, imgBtnShare;
private PostDetails model = null;
// Favourites view
private List<FavouriteModel> favouriteList;
private FavouriteDbController favouriteDbController;
private boolean isFavourite = false;
// Comments view
private List<CommentsAndReplies> commentList;
private List<CommentsAndReplies> zeroParentComments;
List<CommentsAndReplies> onlyThreeComments;
private int mPerPage = 5;
private RecyclerView rvComments;
private CommentsAdapter commentsAdapter = null;
// Text to speech
private TtsEngine ttsEngine;
private boolean isTtsPlaying = false;
private String ttsText;
private WebEngine webEngine;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initVar();
initView();
initFunctionality();
initListener();
}
private void initVar() {
mActivity = PostDetailsActivity.this;
mContext = mActivity.getApplicationContext();
favouriteList = new ArrayList<>();
commentList = new ArrayList<>();
zeroParentComments = new ArrayList<>();
onlyThreeComments = new ArrayList<>();
Intent intent = getIntent();
if (intent != null) {
clickedPostId = getIntent().getIntExtra(AppConstant.BUNDLE_KEY_POST_ID, 0);
}
}
private void initView() {
setContentView(R.layout.activity_post_details);
lytPostDetailsView = (RelativeLayout) findViewById(R.id.lyt_post_details);
lytCommentDetails = (RelativeLayout) findViewById(R.id.lyt_comment_list);
imgPost = (ImageView) findViewById(R.id.post_img);
tvPostTitle = (TextView) findViewById(R.id.title_text);
tvPostAuthor = (TextView) findViewById(R.id.post_author);
tvPostDate = (TextView) findViewById(R.id.date_text);
imgBtnSpeaker = (ImageButton) findViewById(R.id.imgBtnSpeaker);
imgBtnFav = (ImageButton) findViewById(R.id.imgBtnFavourite);
imgBtnShare = (ImageButton) findViewById(R.id.imgBtnShare);
initWebEngine();
tvCommnentList = (TextView) findViewById(R.id.comment_count);
fabWriteAComment = (FloatingActionButton) findViewById(R.id.fab_new_comment);
rvComments = (RecyclerView) findViewById(R.id.rvComments);
rvComments.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
initLoader();
initToolbar();
enableBackButton();
}
public void initWebEngine() {
webView = (WebView) findViewById(R.id.web_view);
webEngine = new WebEngine(webView, mActivity);
webEngine.initWebView();
webEngine.initListeners(new WebListener() {
@Override
public void onStart() {
initLoader();
}
@Override
public void onLoaded() {
hideLoader();
}
@Override
public void onProgress(int progress) {
}
@Override
public void onNetworkError() {
showEmptyView();
}
@Override
public void onPageTitle(String title) {
}
});
}
private void initFunctionality() {
favouriteDbController = new FavouriteDbController(mContext);
favouriteList.addAll(favouriteDbController.getAllData());
for (int i = 0; i < favouriteList.size(); i++) {
if (favouriteList.get(i).getPostId() == clickedPostId) {
isFavourite = true;
break;
}
}
ttsEngine = new TtsEngine(mActivity);
commentsAdapter = new CommentsAdapter(mActivity, (ArrayList) commentList, (ArrayList) onlyThreeComments);
rvComments.setAdapter(commentsAdapter);
showLoader();
loadPostDetails();
}
public void setFavImage() {
if (isFavourite) {
imgBtnFav.setImageDrawable(ContextCompat.getDrawable(mActivity, R.drawable.ic_book));
} else {
imgBtnFav.setImageDrawable(ContextCompat.getDrawable(mActivity, R.drawable.ic_un_book));
}
}
public void initListener() {
imgBtnSpeaker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (model != null) {
toggleTtsPlay();
}
}
});
imgBtnFav.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (model != null) {
isFavourite = !isFavourite;
if (isFavourite) {
String imgUrl = null;
if (model.getEmbedded().getWpFeaturedMedias().size() >= 1) {
imgUrl = model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails().getSizes().getFullSize().getSourceUrl();
}
favouriteDbController.insertData(
model.getID().intValue(),
imgUrl,
model.getTitle().getRendered(),
model.getOldDate(),
model.getEmbedded().getWpTerms().get(0).get(0).getName()
);
} else {
favouriteDbController.deleteFav(clickedPostId);
}
setFavImage();
}
}
});
imgBtnShare.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (model != null) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, model.getPageUrl());
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));
}
}
});
commentsAdapter.setItemClickListener(new ListItemClickListener() {
@Override
public void onItemClick(int position, View view) {
int id = view.getId();
CommentsAndReplies clickedComment = zeroParentComments.get(position);
switch (id) {
case R.id.list_item:
ActivityUtils.getInstance().invokeCommentDetails(mActivity, CommentDetailsActivity.class, (ArrayList) commentList, clickedPostId, clickedComment, false, false);
break;
case R.id.reply_text:
ActivityUtils.getInstance().invokeCommentDetails(mActivity, CommentDetailsActivity.class, (ArrayList) commentList, clickedPostId, clickedComment, true, false);
break;
default:
break;
}
}
});
tvCommnentList.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityUtils.getInstance().invokeCommentList(mActivity,
CommentListActivity.class,
(ArrayList) commentList,
(ArrayList) zeroParentComments,
clickedPostId,
false);
}
});
fabWriteAComment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager manager = getSupportFragmentManager();
WriteACommentFragment dialog = WriteACommentFragment.newInstance(clickedPostId, AppConstant.THIS_IS_COMMENT);
dialog.show(manager, AppConstant.BUNDLE_KEY_DIALOG_FRAGMENT);
}
});
}
public void loadPostDetails() {
ApiUtils.getApiInterface().getPostDetails(clickedPostId).enqueue(new Callback<PostDetails>() {
@Override
public void onResponse(Call<PostDetails> call, Response<PostDetails> response) {
if (response.isSuccessful()) {
// bind data
model = response.body();
PostDetails m = model;
// visible parent view
lytPostDetailsView.setVisibility(View.VISIBLE);
// visible comments button
fabWriteAComment.setVisibility(View.VISIBLE);
loadCommentsAndReplies(model.getLinks().getRepliesList().get(0).getHref());
setFavImage();
tvPostTitle.setText(Html.fromHtml(model.getTitle().getRendered()));
String imgUrl = null;
if (model.getEmbedded().getWpFeaturedMedias().size() > 0) {
if (model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails() != null) {
if (model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails().getSizes().getFullSize().getSourceUrl() != null) {
imgUrl = model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails().getSizes().getFullSize().getSourceUrl();
}
}
}
if (imgUrl != null) {
Glide.with(getApplicationContext())
.load(imgUrl)
.into(imgPost);
}
String author = null;
if (model.getEmbedded().getAuthors().size() >= 1) {
author = model.getEmbedded().getAuthors().get(0).getName();
}
if (author == null) {
author = getString(R.string.admin);
}
tvPostAuthor.setText(Html.fromHtml(author));
String oldDate = model.getOldDate();
String newDate = AppUtils.getFormattedDate(oldDate);
if (newDate != null) {
tvPostDate.setText(Html.fromHtml(newDate));
}
String contentText = model.getContent().getRendered();
ttsText = new StringBuilder(Html.fromHtml(model.getTitle().getRendered())).append(AppConstant.DOT).append(Html.fromHtml(model.getContent().getRendered())).toString();
//webView.loadData(contentText, "text/html", "UTF-8");
contentText = new StringBuilder().append(AppConstant.CSS_PROPERTIES).append(contentText).toString();
webEngine.loadHtml(contentText);
} else {
showEmptyView();
}
}
@Override
public void onFailure(Call<PostDetails> call, Throwable t) {
t.printStackTrace();
// hide common loader
hideLoader();
// show empty view
showEmptyView();
}
});
}
public void loadCommentsAndReplies(final String commentsAndRepliesLink) {
ApiUtils.getApiInterface().getCommentsAndReplies(commentsAndRepliesLink, mPerPage).enqueue(new Callback<List<CommentsAndReplies>>() {
@Override
public void onResponse(Call<List<CommentsAndReplies>> call, Response<List<CommentsAndReplies>> response) {
if (response.isSuccessful()) {
int totalItems = Integer.parseInt(response.headers().get(HttpParams.HEADER_TOTAL_ITEM));
int totalPages = Integer.parseInt(response.headers().get(HttpParams.HEADER_TOTAL_PAGE));
if (totalPages > 1) {
mPerPage = mPerPage * totalPages;
loadCommentsAndReplies(commentsAndRepliesLink);
} else {
if (!commentList.isEmpty() || !zeroParentComments.isEmpty() || !onlyThreeComments.isEmpty()) {
commentList.clear();
zeroParentComments.clear();
onlyThreeComments.clear();
}
commentList.addAll(response.body());
lytCommentDetails.setVisibility(View.VISIBLE);
if (commentList.size() > 0) {
for (CommentsAndReplies commentsAndReplies : commentList) {
if (commentsAndReplies.getParent().intValue() == 0) {
zeroParentComments.add(commentsAndReplies);
}
}
if (zeroParentComments.size() >= 3) {
for (int i = 0; i < 3; i++) {
onlyThreeComments.add(zeroParentComments.get(i));
}
} else {
for (CommentsAndReplies commentsAndReplies : zeroParentComments) {
onlyThreeComments.add(commentsAndReplies);
}
}
commentsAdapter.notifyDataSetChanged();
tvCommnentList.setText(String.format(getString(R.string.all_comment), commentList.size()));
tvCommnentList.setClickable(true);
} else {
tvCommnentList.setClickable(false);
}
}
}
}
@Override
public void onFailure(Call<List<CommentsAndReplies>> call, Throwable t) {
showEmptyView();
t.printStackTrace();
}
});
}
@Override
public void onComplete(Boolean isCommentSuccessful, CommentsAndReplies commentsAndReplies) {
if (isCommentSuccessful) {
loadCommentsAndReplies(model.getLinks().getRepliesList().get(0).getHref());
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}
if (requestCode == AppConstant.REQUEST_CODE_COMMENT) {
if (data == null) {
return;
}
boolean isCommentSuccessful = CommentListActivity.wasCommentSuccessful(data);
if (isCommentSuccessful) {
loadCommentsAndReplies(model.getLinks().getRepliesList().get(0).getHref());
}
}
}
}
还找到build.gradle build.gradle(应用级别)
apply plugin: 'com.android.application'
android {
lintOptions{
checkReleaseBuilds false
abortOnError false
}
compileSdkVersion 29
buildToolsVersion "28.0.3"
defaultConfig {
applicationId "ak.wp.meto"
minSdkVersion 17
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'androidx.appcompat:appcompat:1.0.0'
compile 'com.google.android.material:material:1.0.0'
compile 'androidx.constraintlayout:constraintlayout:1.1.3'
compile 'androidx.cardview:cardview:1.0.0'
//retrofit, gson
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
//glide
compile 'com.github.bumptech.glide:glide:4.3.1'
//circular imageview
compile 'de.hdodenhof:circleimageview:2.1.0'
}
apply plugin: 'com.google.gms.google-services'
我对这个问题的临时解决方案是从我的站点中删除了 postrophe、分号、引号和连字符。 现在一切正常 post 正在完全加载。 在 JSON 中,数据连字符被编码为 – 在 webview 中,在剩余的行消失后仅显示“&”。所以我设法删除了特殊字符。
用那个特殊字符替换你的字符串String contentText = model.getContent().replace("’","'");