仿各類APP將文章DOM轉JSON並在APP中以列表顯示(android、ios、php已開源)

背景

一直以來都想實現相似新聞客戶端、鮮城等文章型app的正文顯示,即在web editor下編輯後存爲json,在app中解析json並顯示正文。java

網上搜過,沒找到輪子。都是給的思路,而後告知是公司項目很差分享代碼,因此乾脆就本身作。node

例子給的ui很粗,以實現功能爲目的,你要有興趣能夠star等我更新。react

輸出的效果看起來是如上圖所示。包括web的編輯器、ios、android。沒作ui美化。android

 

原理

web端

只是爲了驗證功能,因此信息包括標題、內容、其餘數據都是模擬的,輸出的json格式看起來是這樣的:ios

[
    {
        "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規則作好。laravel

規則一句話:umeditor每一個天然段以<p>標籤包圍,遍歷<p>標籤,找出文字和圖片存爲json便可。git

    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。github

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,加了通用的點擊事件web

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重寫了。json

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

相關文章
相關標籤/搜索