仿各种APP将文章DOM转JSON并在APP中以列表显示(android、ios、php已开源)

背景

一直以来都想实现类似新闻客户端、鲜城等文章型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

时间: 2024-10-18 08:10:45

仿各种APP将文章DOM转JSON并在APP中以列表显示(android、ios、php已开源)的相关文章

使用 jQuery Mobile 与 HTML5 开发 Web App 系列文章目录

使用 jQuery Mobile 与 HTML5 开发 Web App 系列文章目录 时间:2012年9月20日 分类:JavaScript 标签:HTML5‚ jQuery Mobile‚ Web App “使用 jQuery Mobile 与 HTML5 开发 Web App”系列文章的数目累积起来也比较多了,为方便大家浏览, Kayo 把这些文章整理成一个目录,收录那些已经写好的文章并会继续更新. 该系列的文章实质上分成四个部分,分别是总体概况.jQuery Mobile 组件.jQuer

iOS App 架构文章推荐

iOS应用开发架构 iOS应用架构谈系列 阿里技术沙龙 2.2.1. Hybrid App 2.2.2. taobao 客户端架构 2.2.3. alipay 客户端架构 iOS APP 架构漫谈 iOS APP 架构漫谈二

使用Json.Net解决MVC中各种json操作

最近收集了几篇文章,用于替换MVC中各种json操作,微软mvc当然用自家的序列化,速度慢不说,还容易出问题,自定义性也太差,比如得特意解决循环引用的问题,比如datetime的序列化格式,比如性能.NewtonSoft.json也就是Json.Net性能虽然不是最好的,但是是比较靠前的,其功能是最强大的,包含各种json操作模式.现在来看看mvc中的替换1, Controller.Json方法这个方法最容易出现循环引用,比如EF查出一个一对多集合想序列化,结果a引用了子表b,b中还引用了a,导

基于微信红包插件的原理实现android任何APP自动发送评论(已开源)

背景 地址:https://github.com/huijimuhe/postman 核心就是android的AccessibilityService,回复功能api需要23以上版本才行. 其实很像在做单元测试.你可以有n种方式实现发帖功能,这只是一个比较邪火的方式,亲测过一次,可行.这里我以网易新闻客户端举例. 模拟你在手机端的物理动作:选择新闻->回复->退回新闻列表->进入下一个新闻->回复->退回新闻列表刷新->进入->回复.... 做的不精细,只是探究到

教育类APP开发现新增长,多款APP该如何突围?

"十二五"以来,国家共出台相关的重大教育政策文件741个,而进入到"十三五"时期教育领域综合改革深入推进的关键期,不断促进教育现代化的实现.加快迈入人力资源强国行列. 政策的大力支持,在线教育市场可谓风起云涌,教育类APP开发也出现新的增长,除了巨额资本的进入,诸多知名线下培训机构也纷纷拓展在线业务,更有各路名师披挂上阵,加入在线教育创业大军.一时间,在线教育市场成为了兵家必争之地.那么谁又将在大混战中突出重围,成为在线教育市场中的独角兽? 在线教育市场的火热,也让

转:?Android IOS WebRTC 音视频开发总结 (系列文章集合)

随笔分类 - webrtc Android IOS WebRTC 音视频开发总结(七八)-- 为什么WebRTC端到端监控很关键? 摘要: 本文主要介绍WebRTC端到端监控(我们翻译和整理的,译者:weizhenwei,校验:blacker),最早发表在[编风网] 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID:blackerteam 或 webrtcorgcn). callstats是一家做实时通讯性能测阅读全文 posted @ 2016-07-22 08:24

推荐下载App,如果本地安装则直接打开本地App(Android/IOS)

推荐下载App,如果本地安装则直接打开本地App(Android/IOS) - 纵观现在每家移动网站,打开首页的时候,都有各种各样的形式来提示你下载自身的移动App(Android/IOS),这是做移动客户端产品的一个很好地引流的手段.当然各家引流下载的交互和视觉各不相同,有的是完全“强奸”用户,有的是完全取悦用户.但是最终的形式就是你点击一个按钮之后,可以去下载对应的App(Android直接下载对应的Apk文件,IOS会跳转到App store的对应地址). 之前开发这个需求的时候,就是很简

可以将一些配置信息已json格式存在数据库中读取的时候序列化。

public partial class json序列化 : System.Web.UI.Page    {        protected void Page_Load(object sender, EventArgs e)        {            StringBuilder builder = new StringBuilder(); builder.Append("{");            builder.Append("    \"C

关于获取Android系统所有已安装App信息的一些操作(详细)

转载请注明出处:http://blog.csdn.net/qinjuning       本节内容是如何获取Android系统中应用程序的信息,主要包括packagename.label.icon.占用大小等.具体分为两个 部分,计划如下:  第一部分: 获取应用程序的packagename.label.icon等 :             第二部分:获取应用程序的占用大小,包括:缓存大小(cachsize).数据大小(datasize). 每部分都为您准备了简单丰富的实例,您一定不会错过.