Android--从零开始开发一款文章阅读APP
前言
本案例已经开源!如果你想免费下载,可以访问我的Github,所有案例均在上面,只求给个star。当然愿意支付小小金额请我喝茶也行(大学穷狗-.-)
一、准备工作
- 使用Android Studio开发
- 微信和QQ第三方sdk,需要自行申请(这个简单)
- 本案例使用干活集中营提供的api,使用MVp+Material Design作为主体架构进行开发
- 体验完整功能,点击下载APK
二、程序实现
目录结构
目录结构如下,我按照功能分包:

实现思路
整体架构--MVP+Material
重点代码分析
如果讲述整个App,估计一篇文章说不清楚。那我干脆取其中一条线来分析。
下面主要分析文章列表--文章详情--文章分享
主页文章列表
这里只选择Android文章模块进行介绍:
GankContract
public interface GankContract {
    interface View extends BaseView<Presenter>{
        //错误
        void showError();
        //正在加载
        void showLoading();
        //停止加载
        void Stoploading();
        //显示数据列表
        void showResult(ArrayList<GankNews.Question> list);
        //网络错误
        void showNotNetError();
    }
    interface Presenter extends BasePresenter{
        // 请求数据
        void loadPosts(int PagerNum, boolean cleaing);
        //刷新数据
        void  reflush();
        //加载更多
        void loadMore(int PagerNum);
        //显示详情
        void StartReading(int positon);
        //随便看看
        void LookAround();
    }
}
GankFragment
Fragment的内容主要是文章列表,我们只分享重点:
//下拉刷新实现
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            boolean isScrollState=false;
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                LinearLayoutManager manager= (LinearLayoutManager) recyclerView.getLayoutManager();
                //没有滚动时候
                if (newState==RecyclerView.SCROLL_STATE_IDLE){
                    //获的最后一个可见的item
                    int lastVisibilityItem=manager.findLastCompletelyVisibleItemPosition();
                    int totalItemCount=manager.getItemCount();
                    //判断是否滚动到底部并且是向下滑动
                    if (lastVisibilityItem==(totalItemCount-1)&&isScrollState){
                        presenter.loadMore(1);
                    }
                }
            }
//通知Presenter加载数据和设置item点击事件
@Override
    public void showResult(ArrayList<GankNews.Question> list) {
        if (adapter==null){
            Log.i(TAG, "showResult: "+list.size());
            adapter=new GankNewsAdapter(list,getContext());
            adapter.setItemOnClickListener(new OnRecyclerViewOnClickListener() {
                @Override
                public void onItemClick(View v, int position) {
                    presenter.StartReading(position);
                }
                @Override
                public void onItemLongClick(View v, int position) {
                }
            });
            recyclerView.setAdapter(adapter);
        }else {
            adapter.notifyDataSetChanged();
        }
    }
GankPresenter
同样只分析重点代码:
//根据当前页数加载列表数据
 @Override
    public void loadPosts(int PagerNum, final boolean cleaing) {
        CurrentPagerNum=PagerNum;
        if (cleaing) {
            view.showLoading();
        }
        if (Network.networkConnected(context)) {
            model.load(Api.Gank_Android + PagerNum, new OnStringListener() {
                @Override
                public void onSuccess(String result) {
                    try {
//                        Log.i(TAG, "gankpresenter.model.load.result"+result);
                        GankNews news = gson.fromJson(result, GankNews.class);
                        //contenvalues只能存储基本类型的数据,像string,int之类的,不能存储对象这种东西,而HashTable却可以存储对象。
//                        ContentValues values = new ContentValues();
                        if (cleaing) {
                            list.clear();
                        }
                        for (GankNews.Question item : news.getResults()) {
                            /**
                             * 1.数据库查重:首先检测数据库中是否已经储存过该条数据
                             * 2:因为每次重启后都是在网络上重新下载数据 如果是数据库已经存在的数据则不会重新加载,也导致了这些数据当前id值为空
                             * ,所有要绑定队友的id值.
                             */
                            if (!queryIfIdExists(item.get_id())){
                                DbLiteOrm.insert(item, ConflictAlgorithm.Replace);
                            }else {
                                ArrayList<GankNews.Question> ganklist=App.DbLiteOrm.query(new QueryBuilder<GankNews.Question>(GankNews.Question.class)
                                        .where(GankNews.Question.COL_ID+"=?",new String[]{item.get_id()}));
                                GankNews.Question gankitem=ganklist.get(0);
                                item.setId(gankitem.getId());
                            }
                            list.add(item);
                        }
                        view.showResult(list);
                    }catch (JsonSyntaxException e){
                        view.showError();
                    }
                   view.Stoploading();
                }
                @Override
                public void onError(VolleyError error) {
                    view.Stoploading();
                    view.showError();
                }
            });
        } else {
            //更新列表缓存 因为详情页都是用webView呈现 所以缓存content为空
            if (cleaing){
                QueryBuilder query=new QueryBuilder(GankNews.Question.class);
                query.appendOrderDescBy("id");
                query.limit(0,10*CurrentPagerNum);
                list.addAll(DbLiteOrm.<GankNews.Question>query(query));
                view.showResult(list);
            }else {
                view.showNotNetError();
            }
        }
    }
//判断数据库是否已经存在
 public boolean queryIfIdExists(String _id){
        ArrayList<GankNews.Question> questionArrayList=App.DbLiteOrm.query(new QueryBuilder(GankNews.Question.class)
                .where(GankNews.Question.COL_ID+"=?",new String[]{_id}));
        if (questionArrayList.size()==0){
            return false;
        }
        return true;
    }
//传递当前点击item的信息,进入详情阅读
@Override
    public void StartReading(int positon) {
        //每个item就是一组数据
        GankNews.Question item=list.get(positon);
        Intent intent = new Intent(context, DetailActivity.class);
        intent.putExtra("type", BeanTeype.TYPE_Gank);
        intent.putExtra("id",list.get(positon).getId());
        int id=list.get(positon).getId();
        Log.i(TAG, "StartReading: "+id);
        intent.putExtra("_id", list.get(positon).get_id());
        intent.putExtra("url",list.get(positon).getUrl());
        intent.putExtra("title", list.get(positon).getDesc());
        if (item.getImages()==null){
            intent.putExtra("imgUrl", "");
        }else {
            intent.putExtra("imgUrl", list.get(positon).getImages().get(0));
        }
        /**
         * Content的startActivity方法,需要开启一个新的task。如果使用 Activity的startActivity方法,
         * 不会有任何限制,因为Activity继承自Context,重载了startActivity方法。
         */
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
//随便看看 随机选取
@Override
    public void LookAround() {
        if (list.isEmpty()){
            view.showError();
            return;
        }
        StartReading(new Random().nextInt(list.size()));
    }
GankNewsAdapter
因为文章分两种:有图和无图。所有要进行分类加载
//判断是否有图和是否是底部加载item
 @Override
    public int getItemViewType(int position) {
        if (position==getItemCount()-1){
            return TYPE_FOOTER;
        }if (list.get(position).getImages()==null){
            return TYPE_NO_IMG;
        }
        return TYPE_NORMTAL;
    }
//根据type加载不同ViewHolder
@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType){
            case TYPE_NORMTAL:
                return new NormalViewHolder(inflater.inflate(R.layout.home_list_item_layout,parent,false),listener);
            case TYPE_FOOTER:
                return new FooterViewHolder(inflater.inflate(R.layout.list_footer,parent,false));
            case TYPE_NO_IMG:
                return new NoImageViewHolder(inflater.inflate(R.layout.home_list_item_without_image,parent,false),listener);
        }
        return null;
    }
//使用Glide加载图片。无图则不加载
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (!(holder instanceof FooterViewHolder)){
            GankNews.Question item=list.get(position);
            if (item!=null){
                if (holder instanceof NormalViewHolder){
                    Glide.with(context)
                            .load(item.getImages().get(0))
                            .asBitmap()
                            .placeholder(R.mipmap.loading)
                            .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                            .error(R.mipmap.loading)
                            .centerCrop()
                            .into(((NormalViewHolder) holder).imageView);
                    ((NormalViewHolder) holder).textView.setText(item.getDesc());
                }else if (holder instanceof NoImageViewHolder){
                    ((NoImageViewHolder) holder).textViewNoImg.setText(item.getDesc());
                }
            }
        }
    }
详情页

DetailActivity
 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.frame);
        if (savedInstanceState!=null){
            detailFragment= (DetailFragment) getSupportFragmentManager().getFragment(savedInstanceState,"detailFragment");
        }else {
            detailFragment=DetailFragment.newInstance();
            getSupportFragmentManager().beginTransaction().replace(R.id.container,detailFragment).commit();
        }
        //获取列表传过来的具体item数据
        Intent intent=getIntent();
        DetailPresenter presenter=new DetailPresenter(detailFragment,DetailActivity.this);
        presenter.setType((BeanTeype) intent.getSerializableExtra("type"));
        presenter.setId(intent.getIntExtra("id",1));
        presenter.set_id(intent.getStringExtra("_id"));
        presenter.setTitle(intent.getStringExtra("title"));
        presenter.setUrl(intent.getStringExtra("url"));
        presenter.setImgUrl(intent.getStringExtra("imgUrl"));
    }
DetailContract
public class DetailContract {
    interface Presenter extends BasePresenter{
        /**
         * 流浪器中打开
         * 复制文本
         * 复制连接
         * 添加收藏或取消收藏
         * 查询是否收藏
         * 请求数据
         * 分享到QQ
         * 分享到微信
         * 分享到朋友圈
         * 分享到微信收藏
         */
        void openInBrower();
        void copyText();
        void copyLink();
        void addToOrDeleteFromBookMarks();
        boolean queryIsBooksMarks();
        void requestData();
        void shareArticleToQQ(final MyQQListener listener);
        void shareArticleToWx();
        void shareArticleToWxCommunity();
        void shareArticleToWxCollect();
    }
    interface View extends BaseView<Presenter> {
        // 显示正在加载
        void showLoading();
        // 停止加载
        void stopLoading();
        // 显示加载错误
        void showLoadingError();
        // 显示分享时错误
        void showSharingError();
        // 正确获取数据后显示内容
//        void showResult(String result);
//        // 对于body字段的消息,直接接在url的内容
        void showResultWithoutBody(String url);
        // 设置顶部大图
        void showCover(String url);
        // 设置标题
        void setTitle(String title);
        // 设置是否显示图片
        void setImageMode(boolean showImage);
        // 用户选择在浏览器中打开时,如果没有安装浏览器,显示没有找到浏览器错误
        void showBrowserNotFoundError();
        // 显示已复制文字内容
        void showTextCopied();
        // 显示文字复制失败
        void showCopyTextError();
        // 显示已添加至收藏夹
        void showAddedToBookmarks();
        // 显示已从收藏夹中移除
        void showDeletedFromBookmarks();
        void  showNotNetError();
        void shareSuccess();
        void shareError();
        void shareCancel();
    }
}
DetailFragment
详情页主题是使用WebView显示,重点注意好设置属性和正确销毁:
 @Override
    public void initView(View view) {
        ......
        //webview设置属性
        webview.getSettings().setJavaScriptEnabled(true);
        //缩放,设置为不能缩放可以防止页面上出现放大和缩小的图标
        webview.getSettings().setBuiltInZoomControls(false);
        //缓存
        webview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
        //开启DOM storage API功能
        webview.getSettings().setDomStorageEnabled(true);
        //开启application Cache功能
        webview.getSettings().setAppCacheEnabled(false);
        .....
    }
//早onDestroy中销毁WebView的对象
@Override
    public void onDestroyView() {
        super.onDestroyView();
        webview.removeAllViews();
        webview.destroy();
        webview=null;
    }
DetailPresenter
//复制链接地址
    @Override
    public void copyLink() {
        ClipboardManager manager= (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData data=null;
        switch (type){
            case TYPE_Gank:
                data=ClipData.newPlainText("text",url);
        }
        manager.setPrimaryClip(data);
        view.showTextCopied();
    }
//添加到收藏或者移除收藏
    @Override
    public void addToOrDeleteFromBookMarks() {
        switch (type){
            case TYPE_Gank:
                GankNews.Question gank= App.DbLiteOrm.queryById(id,GankNews.Question.class);
                if (queryIsBooksMarks()){
                    view.showDeletedFromBookmarks();
                    gank.mark=false;
                }else {
                    view.showAddedToBookmarks();
                    gank.mark=true;
                }
                App.DbLiteOrm.update(gank);
                break;
            case TYPE_Front:
                FrontNews.Question front=App.DbLiteOrm.queryById(id,FrontNews.Question.class);
                if (queryIsBooksMarks()){
                    view.showDeletedFromBookmarks();
                    front.mark=false;
                }else {
                    view.showAddedToBookmarks();
                    front.mark=true;
                }
                App.DbLiteOrm.update(front);
                break;
            case TYPE_IOS:
                IosNews.Question ios=App.DbLiteOrm.queryById(id,IosNews.Question.class);
                if (queryIsBooksMarks()){
                    view.showDeletedFromBookmarks();
                    ios.mark=false;
                }else {
                    view.showAddedToBookmarks();
                    ios.mark=true;
                }
                App.DbLiteOrm.update(ios);
        }
    }
//查询是否已经收藏
    @Override
    public boolean queryIsBooksMarks() {
        if (_id ==null || type==null){
            view.showLoadingError();
            return false;
        }
        //true为已经收藏 false未收藏
        switch (type){
            case TYPE_Gank:
                GankNews.Question gank= App.DbLiteOrm.queryById(id,GankNews.Question.class);
                OrmLog.i(TAG,gank);
                boolean isMark=gank.mark;
                if (isMark){
                    return true;
                }else {
                    return false;
                }
            case  TYPE_Front:
                FrontNews.Question front=App.DbLiteOrm.queryById(id,FrontNews.Question.class);
                if (front.mark){
                    return true;
                }else {
                    return false;
                }
            case TYPE_IOS:
                Log.i(TAG, "queryIsBooksMarks: "+id);
                IosNews.Question ios=App.DbLiteOrm.queryById(id,IosNews.Question.class);
                OrmLog.i(TAG,ios);
                if (ios.mark){
                    return true;
                }else {
                    return false;
                }
        }
        return false;
    }
//分享到QQ
    @Override
    public void shareArticleToQQ(MyQQListener listener) {
        //title == desc
        if (TextUtils.isEmpty(imgUrl)){
            ShareSingleton.getInstance().shareToQQ((Activity) context,url,"推荐给你一篇文章",title, R.string.app_name, QQShare.SHARE_TO_QQ_FLAG_QZONE_ITEM_HIDE,listener);
        }else {
            ShareSingleton.getInstance().shareToQQ((Activity) context,url,"推荐给你一篇文章",title,imgUrl,R.string.app_name, QQShare.SHARE_TO_QQ_FLAG_QZONE_ITEM_HIDE,listener);
        }
    }
//分享到微信
    @Override
    public void shareArticleToWx() {
        //title == desc
        ShareSingleton.getInstance().shareWebToWx(url,"",title,true);
    }
//分享到朋友圈
    @Override
    public void shareArticleToWxCommunity() {
        //title == desc
        ShareSingleton.getInstance().shareWebToWx(url,"",title,false);
    }
//分享到微信收藏
    @Override
    public void shareArticleToWxCollect() {
        //title == desc
        ShareSingleton.getInstance().shareWebToWxCollect(url,"干货",title);
    }
ShareSingleton
关于微信和QQ分享的具体方法还得参考官方文章,我这里提出我自己写好的分享单例类
public class ShareSingleton {
    private Tencent mTencent;
    public static IWXAPI api;
    private static final int THUMB_SIZE = 150;
//单例模式
    private ShareSingleton() {
    }
    public static final ShareSingleton getInstance(){
        return Singleton.INSTANCE;
    }
    private static class Singleton{
        private static final ShareSingleton INSTANCE=new ShareSingleton();
    }
    /**
     * 图文分享 图片来源网络
     * !! 分享操作要在主线程中完成
     * @param activity
     * @param targetUrl  这条分享消息被好友点击后的跳转URL。
     * @param shareTitle 	分享的标题, 最长30个字符。
     * @param shareSummary 分享的消息摘要,最长40个字。
     * @param netImgUrl 可填 分享图片的URL或者本地路径
     * @param appName 手Q客户端顶部,替换“返回”按钮文字,如果为空,用返回代替
     * @param shareToQQExtInt 额外选项  是否自动打开分享到QZone的对话框
     * @param listener 分享回调接口
     */
    public void shareToQQ(Activity activity,String targetUrl,String shareTitle,String shareSummary,
                          @Nullable String netImgUrl,@StringRes int appName,int shareToQQExtInt,MyQQListener listener){
        if (mTencent==null){
            mTencent=Tencent.createInstance(Constants.QQ_APP_ID,activity.getApplicationContext());
        }
        final Bundle params = new Bundle();
        params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);
        params.putString(QQShare.SHARE_TO_QQ_TARGET_URL,targetUrl);
        params.putString(QQShare.SHARE_TO_QQ_TITLE, shareTitle);
        params.putString(QQShare.SHARE_TO_QQ_SUMMARY, shareSummary );
        params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL,  netImgUrl);
        params.putString(QQShare.SHARE_TO_QQ_APP_NAME,activity.getString(appName));
        params.putInt(QQShare.SHARE_TO_QQ_EXT_INT,  shareToQQExtInt);
        mTencent.shareToQQ(activity, params, listener);
    }
    /**
     * 文章分享 无图
     * !! 分享操作要在主线程中完成
     * @param activity
     * @param targetUrl  这条分享消息被好友点击后的跳转URL。
     * @param shareTitle 	分享的标题, 最长30个字符。
     * @param shareSummary 分享的消息摘要,最长40个字。
     * @param appName 手Q客户端顶部,替换“返回”按钮文字,如果为空,用返回代替
     * @param shareToQQExtInt 额外选项  是否自动打开分享到QZone的对话框
     * @param listener 分享回调接口
     */
    public void shareToQQ(Activity activity,String targetUrl,String shareTitle,String shareSummary
                          ,@StringRes int appName,int shareToQQExtInt,MyQQListener listener){
        if (mTencent==null){
            mTencent=Tencent.createInstance(Constants.QQ_APP_ID,activity.getApplicationContext());
        }
        final Bundle params = new Bundle();
        params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);
        params.putString(QQShare.SHARE_TO_QQ_TARGET_URL,targetUrl);
        params.putString(QQShare.SHARE_TO_QQ_TITLE, shareTitle);
        params.putString(QQShare.SHARE_TO_QQ_SUMMARY, shareSummary );
        params.putString(QQShare.SHARE_TO_QQ_APP_NAME,activity.getString(appName));
        params.putInt(QQShare.SHARE_TO_QQ_EXT_INT,  shareToQQExtInt);
        mTencent.shareToQQ(activity, params, listener);
    }
     /**
     * 分享文章到微信/朋友圈
     * @param webUrl
     * @param webTitle
     * @param webDesc
     * @param isShareFriend
     */
    public void shareWebToWx(@NonNull String webUrl,String webTitle,String webDesc,boolean isShareFriend){
//        注册操作也可以写死在Application中
        // 通过WXAPIFactory工厂,获取IWXAPI的实例
        api=WXAPIFactory.createWXAPI(App.getContext(),Constants.WX_APP_ID,true);
        // 将该app注册到微信
        api.registerApp(Constants.WX_APP_ID);
        //初始化一个WXWebpageObject对象,填写url
        WXWebpageObject webpag=new WXWebpageObject();
        webpag.webpageUrl=webUrl;
        //用WXWebpageObject对象初始化一个WXMediaMessage对象  填写标题和描述
        WXMediaMessage msg=new WXMediaMessage(webpag);
        msg.title=webTitle;
        msg.description=webDesc;
        //构造一个Req
        SendMessageToWX.Req req=new SendMessageToWX.Req();
        req.transaction=buildTransaction("webpage");//transaction 字段用于唯一标识一个请求
        req.message= msg;
        req.scene=isShareFriend ? SendMessageToWX.Req.WXSceneSession : SendMessageToWX.Req.WXSceneTimeline;
        api.sendReq(req);
    }
    /**
     * 分享文章到微信收藏
     * @param webUrl
     * @param webTitle
     * @param webDesc
     */
    public void shareWebToWxCollect(@NonNull String webUrl, String webTitle, String webDesc){
//        注册操作也可以写死在Application中
        // 通过WXAPIFactory工厂,获取IWXAPI的实例
        api=WXAPIFactory.createWXAPI(App.getContext(),Constants.WX_APP_ID,true);
        // 将该app注册到微信
        api.registerApp(Constants.WX_APP_ID);
        //初始化一个WXWebpageObject对象,填写url
        WXWebpageObject webpag=new WXWebpageObject();
        webpag.webpageUrl=webUrl;
        //用WXWebpageObject对象初始化一个WXMediaMessage对象  填写标题和描述
        WXMediaMessage msg=new WXMediaMessage(webpag);
        msg.title=webTitle;
        msg.description=webDesc;
        //构造一个Req
        SendMessageToWX.Req req=new SendMessageToWX.Req();
        req.transaction=buildTransaction("webpage");//transaction 字段用于唯一标识一个请求
        req.message= msg;
        req.scene=SendMessageToWX.Req.WXSceneFavorite;
        api.sendReq(req);
    }
这篇文章就分析这么多,如果你想了解跟多,欢迎下载源码。主要部分源码都有注释
三、部分运行效果


四、其他补充
如果你有问题可以提交到Github的issue上,也可以给我发邮件。我的邮件是yeshuwei.swy@gmail.com
Android--从零开始开发一款文章阅读APP
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
Android--从零开始开发一款文章阅读APP的更多相关文章
- Android开源实战:使用MVP+Retrofit开发一款文字阅读APP
		文字控 使用MVP+Retrofit开发的一款文艺APP,它是一个非常优美的文字阅读应用,界面基本上符合material design设计规范. 在该项目中,我采用的是MVP架构,该架构目前在Andr ... 
- 基于WanAndroid开放API实现的文章阅读APP
		简介 基于WanAndroid开放API开发的技术文章阅读App.主要功能包括:首页.体系.项目.公众号.搜索.登录.收藏.夜间模式等. 用到的第三方框架 RxJava RxAndroid Retro ... 
- 从零开始开发一款app,所想到的
		我在知乎上看到这个问题http://www.zhihu.com/question/27645587.我在阅读了各位大牛的答案后,再加上自己的思考,就有了这篇文章的内容. 从零开始开发一款app ... 
- 开发一款即时通讯App,从这几步开始
		欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯云视频发表于云+社区专栏 关注公众号"腾讯云视频",一键获取 技术干货 | 优惠活动 | 视频方案 " ... 
- 从零开始开发一款H5小游戏(二) 创造游戏世界,启动发条
		本系列文章对应游戏代码已开源 Sinuous game 上一节介绍了canvas的基础用法,了解了游戏开发所要用到的API.这篇文章开始,我将介绍怎么运用这些API来完成各种各样的游戏效果.这个过程更 ... 
- 从零开始开发一款H5小游戏(三) 攻守阵营,赋予粒子新的生命
		本系列文章对应游戏代码已开源 Sinuous game. 每个游戏都会包含场景和角色.要实现一个游戏角色,就要清楚角色在场景中的位置,以及它的运动规律,并能通过数学表达式表现出来. 场景坐标 canv ... 
- 如何开发一款堪比APP的微信小程序(腾讯内部团队分享)
		一夜之间,微信小程序刷爆了行业网站和朋友圈,小程序真的能如张小龙所说让用户"即用即走"吗? 其功能能和动辄几十兆安装文件的APP相比吗? 开发小程序,是不是意味着移动应用开发的一次 ... 
- [Android游戏开发]八款开源 Android 游戏引擎 (巨好的资源)
		初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引 ... 
- Android商城开发系列(二)——App启动欢迎页面制作
		商城APP一般都会在应用启动时有一个欢迎界面,下面我们来实现一个最简单的欢迎页开发:就是打开商城App,先出现欢迎界面,停留几秒钟,自动进入应用程序的主界面. 首先先定义WelcomeActivity ... 
随机推荐
- 【BZOJ 1078】 1078: [SCOI2008]斜堆
			1078: [SCOI2008]斜堆 Description 斜堆(skew heap)是一种常用的数据结构.它也是二叉树,且满足与二叉堆相同的堆性质:每个非根结点的值都比它父亲大.因此在整棵斜堆中, ... 
- WPS设置去广告
			韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha WPS设置去广告 设置密码和权限 
- 【POJ 2409】Let it Bead
			http://poj.org/problem?id=2409 Burnside引理:设\(G\)是\(X\)的置换群,而\(\mathcal{C}\)是\(X\)中一个满足下面条件的着色集合:对于\( ... 
- 2017 Multi-University Training 2 解题报告
			Is Derek lying? Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)T ... 
- BZOJ 4884 [Lydsy2017年5月月赛]太空猫(单调DP)
			[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=4884 [题目大意] 太空猫(SpaceCat)是一款画面精致.玩法有趣的休闲游戏, 你 ... 
- 【动态规划+高精度】mr360-定长不下降子序列
			[题目大意] 韵哲君发现自己的面前有一行数字,当她正在琢磨应该干什么的时候,这时候,陈凡老师从天而降,走到了韵哲君的身边,低下头,对她耳语了几句,然后飘然而去. 陈凡老师说了什么呢,陈凡老师对韵哲君说 ... 
- ncnn阅读 - CMakeLists.txt
			CMAKE_TOOLCHAIN_FILE This variable is specified on the command line when cross-compiling with CMake. ... 
- fedora19/opensuse13.1 配置svn client
			Date: 20140208Auth: Jin 一.install zypper install subversion yum install subversion 二.操作 1.将文件check ... 
- NHibernate官方文档中文版--基础ORM(Basic O/R Mapping)
			映射声明 对象/关系映射在XML文件中配置.mapping文件这样设计是为了使它可读性强并且可修改.mapping语言是以对象为中心,意味着mapping是围绕着持久化类声明来建立的,而不是围绕数据表 ... 
- for of 与 for in的区别2
			遍历数组通常使用for循环,ES5的话也可以使用forEach,ES5具有遍历数组功能的还有map.filter.some.every.reduce.reduceRight等,只不过他们的返回结果不一 ... 
