背景
一直以來都想實作類似新聞用戶端、鮮城等文章型app的正文顯示,即在web editor下編輯後存為json,在app中解析json并顯示正文。
網上搜過,沒找到輪子。都是給的思路,然後告知是公司項目不好分享代碼,是以幹脆就自己做。
例子給的ui很粗,以實作功能為目的,你要有興趣可以star等我更新。
輸出的效果看起來是如上圖所示。包括web的編輯器、ios、android。沒做ui美化。
原理
web端
隻是為了驗證功能,是以資訊包括标題、内容、其他資料都是模拟的,輸出的json格式看起來是這樣的:
[
{
"id": 2,
"title": "fdfd",
"text": "[{\"object\":\"text1\",\"type\":2},{\"object\":\"http:\\/\\/gitwiduu.u.qiniudn.com\\/lanwen_14637283563254.jpg\",\"type\":3}]",
"author": "作者名稱",
"created_at": "2016-05-20 15:12:38",
"updated_at": "2016-05-20 15:12:38"
},
{
"id": 3,
"title": "adfadf",
"text": "[{\"object\":\"text1adsfdasf\",\"type\":2}]",
"author": "作者名稱",
"created_at": "2016-05-20 15:22:49",
"updated_at": "2016-05-20 15:22:49"
},
{
"id": 4,
"title": "adfadf",
"text": "[{\"object\":\"text1\",\"type\":2}]",
"author": "作者名稱",
"created_at": "2016-05-20 15:23:07",
"updated_at": "2016-05-20 15:23:07"
}
]
web端基于laravel4.2開發,采用的umeditor和七牛雲伺服器上傳圖檔(這部分已剝離單獨開源),。我仔細研究了一下,可能需要更進一步自定義umeditor,把圖檔parse規則做好。
規則一句話:umeditor每個自然段以<p>标簽包圍,周遊<p>标簽,找出文字和圖檔存為json即可。
public function getDom($text) {
$dom = new DOMDocument();
$dom->loadHTML($text);
$node = $dom->documentElement;
$tempArray = false;
//周遊所有節點
$childs = $node->getElementsByTagName('p');
foreach ($childs as $child) {
//文字
if ($child->nodeValue) {
$tempArray[] = ['object' => $child->nodeValue, 'type' => 2];
}
//圖檔
$imgs = $child->getElementsByTagName('img');
if ($imgs) {
foreach ($imgs as $img) {
$tempArray[] = ['object' => $img->attributes->getNamedItem('src')->nodeValue, 'type' => 3];
}
}
}
return json_encode($tempArray);
}
android端
android端基于RecyclerView,主要考慮的地方是在adapter中需要根據type來輸出不同的view,我用之前獨白故事項目的思路(是基于以前看過一篇文章大神的思路),把不同type類型封裝為“render”,然後根據資料的type來确定顯示什麼view。
package com.huijimuhe.lanwen.adapter.base;
import android.annotation.TargetApi;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractRenderAdapter<T> extends RecyclerView.Adapter<AbstractViewHolder> {
public static final int BTN_CLICK_ITEM = 0;
public ArrayList<T> mDataset;
public onItemClickListener mOnItemClickListener;
protected View mHeaderView;
@TargetApi(4)
public AbstractViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
return null;
}
@Override
public int getItemCount() {
return mHeaderView == null ? mDataset.size() : mDataset.size() + 1;
}
public List<T> getList() {
return this.mDataset;
}
public int getRealPosition(int position) {
return mHeaderView == null ? position : position - 1;
}
public T getItem(int position) {
return mDataset.get(getRealPosition(position));
}
public void setOnItemClickListener(onItemClickListener l) {
mOnItemClickListener = l;
}
public interface onItemClickListener {
void onItemClick(View view, int postion, int type);
}
public void setHeaderView(View view) {
mHeaderView = view;
}
public View getHeaderView() {
return mHeaderView;
}
}
上面的代碼是基礎baseadapter,加了通用的點選事件
package com.huijimuhe.lanwen.adapter;
import android.annotation.TargetApi;
import android.view.ViewGroup;
import com.huijimuhe.lanwen.adapter.base.AbstractRender;
import com.huijimuhe.lanwen.adapter.base.AbstractRenderAdapter;
import com.huijimuhe.lanwen.adapter.base.AbstractViewHolder;
import com.huijimuhe.lanwen.adapter.render.ImageSectionRender;
import com.huijimuhe.lanwen.adapter.render.TextSectionRender;
import com.huijimuhe.lanwen.model.SectionBean;
import java.util.ArrayList;
public class SectionAdapter extends AbstractRenderAdapter<SectionBean> {
public static final int RENDER_TYPE_TEXT = 0;
public static final int RENDER_TYPE_IMAGE = 1;
public static final int BTN_CLICK_ITEM = 101;
public static final int BTN_CLICK_IMAGE = 102;
public SectionAdapter(ArrayList<SectionBean> statues) {
this.mDataset = statues;
}
@TargetApi(4)
public AbstractViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
//header view 的判斷
AbstractViewHolder holder = super.onCreateViewHolder(viewGroup, viewType);
if (holder != null) {
return holder;
}
switch (viewType) {
case RENDER_TYPE_TEXT: {
TextSectionRender head = new TextSectionRender(viewGroup, this);
AbstractViewHolder headHolder=head.getReusableComponent();
headHolder.itemView.setTag(android.support.design.R.id.list_item,head);
return headHolder;
}
case RENDER_TYPE_IMAGE: {
ImageSectionRender head = new ImageSectionRender(viewGroup, this);
AbstractViewHolder headHolder=head.getReusableComponent();
headHolder.itemView.setTag(android.support.design.R.id.list_item,head);
return headHolder;
}
default:
return null;
}
}
@TargetApi(4)
public void onBindViewHolder(AbstractViewHolder holder, int position) {
AbstractRender render = (AbstractRender) holder.itemView.getTag(android.support.design.R.id.list_item);
render.bindData(position);
}
@Override
public int getItemViewType(int position) {
int type = getItem(position).getType();
switch (type) {
case 2:
return RENDER_TYPE_TEXT;
case 3:
return RENDER_TYPE_IMAGE;
default:
return 0;
}
}
}
這是文章的adapter,可以看到getItemViewType重寫了。
package com.huijimuhe.lanwen.adapter.render;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.huijimuhe.lanwen.R;
import com.huijimuhe.lanwen.adapter.SectionAdapter;
import com.huijimuhe.lanwen.adapter.base.AbstractRender;
import com.huijimuhe.lanwen.adapter.base.AbstractRenderAdapter;
import com.huijimuhe.lanwen.adapter.base.AbstractViewHolder;
import com.huijimuhe.lanwen.model.SectionBean;
public class TextSectionRender extends AbstractRender {
private ViewHolder mHolder;
private AbstractRenderAdapter mAdapter;
public TextSectionRender(ViewGroup parent, AbstractRenderAdapter adapter) {
this.mAdapter =adapter;
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.listitem_text,parent,false);
this.mHolder=new ViewHolder(v,adapter);
}
@Override
public void bindData(int position) {
SectionBean model=(SectionBean) mAdapter.getItem(position);
mHolder.mTvContent.setText(model.getObject());
}
@Override
public AbstractViewHolder getReusableComponent() {
return this.mHolder;
}
public class ViewHolder extends AbstractViewHolder{
public TextView mTvContent;
public ViewHolder(View v,final AbstractRenderAdapter adapter) {
super(v);
mTvContent = (TextView) v.findViewById(R.id.item_text);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
adapter.mOnItemClickListener.onItemClick(v, getLayoutPosition(), SectionAdapter.BTN_CLICK_ITEM);
}
});
}
}
}
這是文字段落的render。
ios端
其他功能如網絡通路、json轉換不再讨論,都是很常見的。在ios下實作其實簡單,隻需要根據type傳回不同的cell即可,但需要注意的是高度,我圖省事用的固定高度,這一塊會在後續的更新中修正。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
HJSectionModel* section=(HJSectionModel*)self.data[indexPath.row];
switch (section.type) {
case 2:{
HJTextTableViewCell* cell=[tableView dequeueReusableCellWithIdentifier:textIdentifier forIndexPath:indexPath];
cell.text.text=section.object;
return cell;
}
case 3:{
HJImageTableViewCell* cell=[tableView dequeueReusableCellWithIdentifier:imageIdentifier forIndexPath:indexPath];
[cell.image sd_setImageWithURL:section.object];
return cell;
}
default:
return nil;
}
}
項目位址
【WEB】https://github.com/huijimuhe/dom2json-web
【ANDROID】https://github.com/huijimuhe/dom2json-android
【IOS】https://github.com/huijimuhe/dom2json-ios
結論
這樣做和webview相比有幾個好處:
1.讀得快
2.你可以加點自定義控件進去,而且排版不别扭
3.排版可以更好的定制
如果你用react.js之類的h5,請無視這篇文章。其實我也想用,哈哈哈...
做完之後總結起來,下一步要改進的地方:
1.不能像鮮城一樣幾個圖檔存入一個數組
2.可以做個像投票和圖檔輪播的控件,這樣才能顯示出優勢
3.android的adapter是還可以繼續改進的
4.ios的架構可以寫的更易讀
P.S
來App獨立開發群533838427
github:https://github.com/huijimuhe