全站首页设为首页收藏本站

外链之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

社区广播台

    查看: 1|回复: 0
    打印 上一主题 下一主题

    [美国大片] 了解:一个人如何开发一款 App?

    [复制链接]
    跳转到指定楼层
    楼主
    发表于 前天 01:47 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

      想创业搞 App,包括前台、后台、服务器、美工很多东西,身边没有人可以帮忙,自己一个人做得来么?通过学习做要多久?需要经验积累什么的吗?西安APP定制http://www.jiujieseo.cn/西安量子悦动()一直致力于西安网站建设服务,提供网站建设、小程序开发、软件开发等一系列服务,欢迎来电咨询合作!


      从创意到产品到交互到设计到前端到客户端到后端到服务器到运营到市场,我都喜欢自己搞,哈哈。

      做过三个比较拿得出手的作品:

      1.

      颜文字输入法

      一个app,卖出的时候有60W+用户。

      2.

      前端乱炖, 最专业的前端技术内容社区

      一个前端垂直社区,每年大概覆盖100W+UV。

      3.

      二十一点睡前故事:在 App Store 上的内容

      一个我老婆讲故事的app。

      都是业余下班时间搞的。

      所以说,一切皆有可能,别瞎BB直接干,需要学习的东西很多。

      补充:

      1. 年轻的时候我耗费了很多精力学习各种技能,动力就是做一个自己的产品出来并且让他活下去。经常通宵熬夜学习练习试错。现在很少有时间这样为自己拼命了

      2. APP卖了白菜价,对钱没有太大感觉,只求安心,东西做到一定规模要操心的太多了,没那精力,毕竟只是业余项目。

      3. 有时候,我会回忆以前为毛折腾这么多东西,有时候觉得就是为了存在感吧,告诉大家,我能做出不太一样的东西来,我跟身边人略有不同,然后最后并没有卵用。

      4. 以前做过很多东西,工具,类库,插件,做过很多失败的尝试性项目,想想以前真是爱折腾。不过这些都是铺垫,没有这些事情也不可能做出一个拿得出手的东西来。

      5. 有一段时间对设计特别在意,研究各种设计哲学。有一段时间对技术非常热衷,技术栈贯穿前后端客户端等等。有一段时间对产品非常热衷,每天晚上都在河边散步构思产品路径。感觉人生的路很长很长,不要急,一个一个来,只要肯花时间,可以学很多东西。

      有不少同学问我如何学app开发,这事太难说了,编程其实说容易容易,说难也难,看你能不能静下心来学习了。建议去京东随便买几本书看,因为没有编程基础,所以推荐看一些比较初级的国产书,如果是正儿八经学习,还是看评分买吧,进口翻译的书,不过会稍微深奥一些,链接:

      ios开发 - 商品搜索

      我看下面有答案说个人开发者成功率很低,现在iOS开发哀鸿一片,这个应该才是主流声音。还有应用的成功需要推广能力和运气。

      怎么说呢,首先我这款应用并不能算成功,靠应用开发买房买车迎娶白富美的码农不在少数,我这款应用的收益一定比各位想到的要少。任何事情,能做成功的人都是小部分,但那部分人是真正努力的人。但是他们成功都有一个共性,就是做了一款好产品。大部分开发者失败的地方不是在于会不会开发,会不会设计,而是在于不知道如何做一款好产品,甚至很多人不知道什么样的产品才叫好产品。所以应用火了之后就是运气,不火了就是因为自己竞争不过大公司,没有推广能力。但始终不去思考自己的产品哪里有问题,哪里可以做更好,而这才是成功的关键。App Store到现在为止,总体来说还是很公平的,审核团队也很认真负责,一旦你的应用不错,获得推荐的几率还是很大的,所以大家不要泄气。

      说到如何做一款好产品,除了自己的技术和综合能力需要不断提高之外,多看看人家的出色应用,使用的时候去推敲一下产品背后的思路,截图下来做一下笔记。自己做应用的时候别着急写代码,先把想法写下来,多整理推敲一下。也别操之过急,给自己足够的时间不断打磨一款产品。

      ====================================================

      过千赞了,谢谢大家。说到做到,5COINS限免一周(2015年10月5日起)。

      ========原答案=======

      三年了,说一下自己的经历吧。累,真心很累。。。但是一切都很值得。

      我做的第一款应用是一款叫5coins的记账应用,初衷是学会iOS开发。却没想到能经历这么多,当然自己也学到了很多, 分享给大家吧。

      我做iOS开发之前,一直做的是Web开发,不断游走于PHP, Python,HTML,JS, Flex之间。09年底购入一台Macbook Pro之后,一直想搞iOS开发,但每次一看到Objective-C那种奇葩的语法,心里就有马在奔跑,反复折腾几次之后,终于在12年底下定了决心开始钻入iOS开发。

      自己虽然不是设计师,但有一些基本的审美素质,所以在国外网站上20美元买了一套UI后,自己做了设计。交互也是自己画的,太拙劣了,就不晒出来了。

      看上去是这个样子的

      和大多数应用一样,上线后就没动静了,每周偶尔有几个人购买,对!是收费的,0.99美元。之后更新了2、3次,添加了小功能和修复了BUG,但始终没有任何起色。

      转机发生在一年以后,也就是iOS7发布后。看完13年的WWDC,当时被Yahoo天气等惊艳到后,我决定要重新设计一下我的应用了。在一个设计师同事的帮助下,应用就变成了这个样子,图标也重新设计了一下。

      因为支持了64位,当9月份iOS7正式开放下载前一周的时候,我收到了苹果给开发者的邮件说可以开始提交iOS7的应用了,我便于当天就提交了。没有想到的是几天后接到了苹果打来的电话(美国的号码),说我的应用在iOS7上面闪退,这个应该是当时系统的一个BUG,如果你的应用同时支持32位和64位的话就会在64位设备上闪退,因为iPhone5才发布,所以开发过程中并没有机器可以来测试。对方告诉我有两个选择,要么只支持64位系统,那么就不能支持iOS6;要么放弃64位的支持,这样iOS6和7上都能运行,让我决定后重新上传,他会尽快审核。我最后选择了仅支持iOS7和64位并当天就重新提交审核并把价格改成了1.99美元,想着反正没人下,也不在乎这个价格了,苹果很给力,第二天审核就通过了。也正是因为这一次改版,彻底改变了这款应用的命运。

      iOS7正式发布后,每周的下载量略有提升,但还是非常少,完全没有任何办法啊,没钱刷榜只能力求苹果推荐,当时听说支持新特性什么的就更容易获得推荐,所以在13年底的时候花了时间增加了用户呼声最高的iCloud功能,结果是给自己挖了一个大坑,后面详说。反正就这么一直不死不活的样子直到14年1月份。某天晚上睡觉前,我突发奇想把应用改成了免费几天(传说中的限免)。结果第二天早上,收到了一封AppsGoneFree发来的邮件,说他们推荐了我的应用。因为老婆一直用AppsGoneFree下限免应用,所以这个消息让我兴奋了起来,一直在期待统计数据的更新(因为没有加第三方统计库,而苹果的数据统计是一天一更的),晚上看到数据的时候把我乐坏了,1.4万的下载量,看来用户还是喜欢免费的啊。这个结果直接导致了应用的评分也大量增加,并且90%的都是5星评分。而大量的下载也会提升应用在市场上的排名,并带来更多的下载。而且限免结束后一段时间内的购买也会有提升,我猜想应该是用户基数上去了,知名度更高的原因。总而言之,这次限免让我尝到了甜头,因此每次更新的时候我都会顺便限免一次。

      14年3月份的时候限免又被AppsGoneFree推荐,那次日下载量突破了3万,并且在各大(小)应用市场中的排名蹭蹭往上窜。

      然后3月27日早上收到了一封用户邮件,他说他在新闻里看到我的应用,里面说有安卓版,但是他没有找到。我马上问他在哪个新闻看到的,于是他把地址给我了。是美国Fox6台的一个地方新闻中的一个栏目叫做What’s Appening,主要是向用户介绍最近流行的应用,节目中他们还特意准备了5张1美元来作为开场白的道具,略带喜感。下面是视频截图,可以看到主持人手中的手机中运行着5coins。

      14年9月,被中国市场首页推荐

      15年1月,被Product Hunt推荐,当天日下载量达到近13万

      期间,网站也改版了好几次,下面是新版后的第一个网站(Landing Page)

      第二次改版

      现在的样子 5coins - The simplest expenses tracking app for iOS

      =================但是,出来混的,迟早是要还的。==========

      由于自己的失误,加上陷入iCloud的坑,一次更新后出现大量用户数据丢失的情况,每天都会收到几十封来自用户的邮件,问数据怎么没了,我要一封封回,并且要尽量帮用户找回数据,要问用户的系统版本,要问iCloud设置,要告诉用户可以通过哪些方法将数据弄回来。同时你要去想办法重现这个问题,接触过iCloud的开发者应该明白,CoreData(本地数据库)+iCloud是噩梦般的存在,现在应该好一些了,特别是CloudKit出来之后。总之,我需要一边安抚用户的情绪,一边努力找到BUG,提交新版本,向苹果申请快速审核(苹果很给力,两次申请都通过了,提交24小时内就通过了审核)。

      但用户是没有耐心的,特别是丢失了数据的用户。所以App Store出现了很多一星的评价,给大家看两个最让我难受的:

      LOVED it until data was deleted

      This was a very useful app... I used it daily to keep expense reports in check. Until the latest update 3.4.4. Everything has been deleted. 6 months worth of data. GONE. Fix or app is getting deleted like my data.

      简单翻译一下:你特么不修复这个BUG我就删了你这个应用,就像你删了我的数据一样!

      Thanks for the horrible update

      This was a pretty cool program when I got it in January. I really liked the way my spending patterns changed when I could visualize where my money went. But this is also an archival app for your expenses. Today they released an update and every single expense I had on the app prior to the update disappeared. I uninstalled and will avoid this app developer in the future.

      简单翻译一下:数据全丢,太失望了,删应用,以后离这个开发者远远的。

      那几天,心里非常难过,也非常后悔,后悔自己的错误给这么多用户造成了损失。

      ==========================================

      以上就是我作为一个独立开发者做一款应用的过程,有机遇,也会有挫折,但真正能让你坚持下去的是还是内心的那份喜爱。以下是一些心得,回答题主的问题,希望也给各位想自己做应用的开发者一些启示。

      1. 认真对待每一个功能

      不稳定,bug多多的功能还是别上了,那是给自己找罪受。要支持一个系统的新特性?别着急上线,仔细阅读一下官方文档,网上看看人家的经验,争取找一个最合理的方案(Best Practices)。你今天心急随意上了一个功能,说不定哪天就会给用户带来不可挽回的损失。独立开发者意味着你需要承担更多的责任。

      2. 时间总是有的

      没时间不是理由,说自己时间不够的有两种人,一种是真时间不够,还有一种是懒,仔细想想你属于哪类?我家在无锡,工作在上海,5coins的大部分代码都是我周末在往返动车上完成的,周末时间不能用,因为要约会,哈哈。什么?程序员也有女朋友?当然有!而且还很漂亮!已经成为老婆了。

      3. 不要停止学习

      做独立开发者的最大的一个好处就是你可以不断学习你想要学习的东西,新的技术,新的平台特性,新的编程语言,你的全职工作很多时候并不会允许你有这么多机会自己去完新的东西,所以开发者利用自己的项目去学习是非常必要的。

      4. 如何成为设计师

      有设计师朋友一起做当然是最好,不过很多时候设计师都要你自己来当,但要记住一个原则,交互设计大于视觉设计。首先要好用,其次再是好看。至于如何把交互设计做好,这里的学问就多了,我也只是在学习过程中,除了对设备本身的特性要熟悉之外,最重要的是站在用户的角度去思考问题。视觉设计的话,自己实在没感觉就去买一套UI吧,也不贵,比你自己瞎整更省时间。现在老婆在我的引hu导you下也正在转行学设计的过程中。这是和她合作的第一款应用(大姨妈管理应用, 名字叫Lunaria,免费的哦),怎么样?还是有点感觉的吧。我做这款应用是为了学习swift。

      5. 需要累积什么经验么?

      你经验越丰富,走的弯路也就越少。但丰富的经验不是前提,因为作为独立开发者这个过程本身也是经验积累的过程。

      好了,先到这里吧,这是我知乎最长的答案了,也不知道有没有人看。能看到这里的同学看在我辛苦的份上也点个赞吧,赞多了,开心了,我直接把应用限免了,哈哈。

      先回答题主的问题:可以的

      其实能自己做一款APP很多时候是一件很自由的事。自己定需求,自己做交互、UI,自己写代码……当然也是一件很累的事。

      先贴截图:

      1.0版本

      2.0版本

      下载链接:

      「Corner」安卓版免费下载

      它叫Corner,为什么会实现它呢,只能说脑洞开得大,经常会冒出一些奇怪的点子,但很多时候都是因为没能力实现而觉得很遥远,但慢慢的你会发现,并不是没能力,而是没毅力。

      (具体想知道Corner是干嘛的,请移步这两个帖子:

      北邮人论坛-北邮人的温馨家园北邮人论坛-北邮人的温馨家园

      )

      我就说说题主的提到几个点吧。

      定需求:

      怎么去定需求,我算是野路子,没写需求文档(也用不着写),更谈不上市场调研、需求分析什么的了,正如乔布斯说的,如果不是真正把一款产品摆到用户面前,用户是不知道他到底想要什么的。所以全部需求都是照着我的初衷和直觉来定的,好像也只能这么做了。

      1.0版本在原来“教室吐槽”和“自习室反馈”的基础上,爬了教务处的空教室数据(数据是每天更新的,只显示当天),增加一个空教室查询功能。

      2.0版本在原来的基础上增加“宿舍楼聊天”和“身边的人”的功能。

      一开始我觉得定需求so easy,直到这个暑假实习后才发现,一个需求是要需要经过好几番讨论、分析……才能确定的。产品经理,技术人员,谁都有谁的不可替代性。

      服务器:

      看到这么多人都说这个题是leancloud的广告贴,好吧,其实我用的就是leancloud==,起初还不知道leancloud的时候,感觉要自己搭一台服务器的话,学东学西的,做完这个APP也得一年半载吧,还好有个舍友跟我说了leancloud,同时他也说了其他的,比如:阿里云,京东的什么什么,新浪的什么什么……但leancloud的学习成本相对小一些,于是就选了它,虽然时不时服务器维修什么的,但还是不错啦~当然类似平台也有很多,像环信、友盟……

      Android开发:

      大一的时候看到上铺自学Android开发,于是暑假也跟着凑热闹自学了两个月,之后因为别的事停了一段时间。所以代码开始写时,Android就没学几个月,自然地,一些现成的优秀的轮子都没用,比如网络请求,图片缓存……如果当初用了那些成熟的轮子,或许开发会更顺畅些,但是,你始终不知道人家里面都写了什么,比如一开始就使用Fresco,可能我就不知道怎么用线程池、建立二级缓存、避免OOM……了,所以,塞翁失马,焉知非福,开始学的时候,建议还是自己亲自去体会一些内在的基本的东西为好。顺便一说,Java也是自己看了一些语法就开始写了,很多人在纠结学Android需不需要先把Java学好,我只能说想把Android学好,一定要把Java学好,别问我是怎么知道的~

      另外,开发环境也是很重要的,不要再问Eclipse,IDEA,AS哪个好了,选AS吧~

      交互、UI:

      对于交互,我相信每个人使用任何一款APP的时候,都会多多少少有一些吐槽,至少我是这样的。所以在设计交互的时候,我能做的也只是尽量避免那些自己认为很反人类的设计了。

      而至于UI,身为一个通信工程的大二狗除了上课、写代码、做实验,实在无力去学习怎么作图了~==,扬长避短,既然不会P图,那就直接用现成的好了,在纸上把大致UI图画好后,具体图标在网上找,偶然在github找到了整套Google Android 5.0 的icon,于是APP里的那些icon都用的这套图标。另外,一个很重要的技能就是配色了,在1.0版本的时候,会去纠结各种配色,为此还去看了一些配色的知识。比如当初就很纠结要选下面哪种方案好。

      到了2.0的时候,就往MD风格靠了,借鉴很多他的配色,当然,这只是表面上的MD,我觉得MD的精髓还是在其动画,也就是他的交互。

      题主在纠结是否要自己去做一款APP的话,如果是创业的话,毕竟是要将其商业化,还是三思后行为好,如果是为了学习的话,劝你赶快行动吧,制作过程是一个痛并快乐着的过程,比如我那段时间就经常上课画大致的UI图,这是个会让你上瘾的过程。且不论成功与否,有没有人用,能让一个idea落地生根的变为现实,这本身就是一件很有成就感的事,在外人眼里,他们也许觉得你的APP没人用,很失败!对此,只能说:冷暖自知。别人始终不知道你从中学到了什么,体会到了什么。管他的呢,follow your heart!

      共勉!

      看到这个问题不免一颤,哈哈 这不就是自己这几个月开发App一步步走来的过程么。在此跟各位分享一下如何从零开发一个App以及后续。笔者也是初出茅庐~但也是从零自己奋斗上来,所以有些经验可以分享,高手大大轻喷(′?_?`)

      笔者还在读书,本科。两个学期前我对于App开发还是一无所知,从零起步。多从零呢,大概就是谭浩强的C语言教材我都看的云里雾里吧。先分享一下我的个人经历。今年3月我有一个好的App创意,而且想来应该是非常简单的开发过程。于是开始自学App开发。一个月后基本熟悉了C语言及苹果Swift语言,略微了解苹果的应用开发框架,便哐当哐当开始自己的第一款应用开发。实践便是最好的老师!于是今年5月个人第一款App上架。一个很简单的GPA计算器:

      虽然在很多人眼里只是很简单的事,但对自己鼓励很大!于是完全没有休息,在不停继续学习必要知识的同时开始开发自己的第二款App。过程大概就是发现新需求,学习如何实现,然后真正实现到自己的App。第二款App Morse Input于今年6月上架。是一款用莫尔斯电码

      做输入方式的输入法:

      然后呢?然后就放暑假啦……… 学生党伤不起摊手( ̄▽ ̄)

      暑假期间回了两周的老家。老家没网,没wifi… 对我简直折磨(╥﹏╥)… 于是抱了两本苹果2D游戏开发框架SpriteKit教材走。准备闲来翻翻。结果一开始看就停不下来了。这不是很简单嘛!比开发App还简单。而且很有意思嘛开发游戏什么的!笔者此前对游戏绝不算发烧~ 痴迷过经典掌机游戏Pokemon,喜欢主机大作GTA,其它游戏几乎没玩过ORZ…… 但开发游戏真的很吸引人。可以制定一个小小世界的规则,所有Sprite的生死都在我手上吼吼吼吼吼。于是暴走看书两周看完了SpriteKit基本知识并开始开发自己第一款游戏,游戏于今年10月上架(游戏开发周期真是长啊… 伤不起)有人可能会问美工怎么解决的?这个也让笔者头疼好久… 最后还是决定自己画。在pad上下了个叫Dots的像素风绘画App:

      自己手绘素材。因为都是像素风的所以还是比较容易画的… 然后等游戏完成后又觉得略单薄,于是找自己会画画的朋友给自己的游戏画了另一套手绘主题。现在游戏类似涂鸦跳跃一样可以切换主题。

      游戏上市后一周,自己开发的几款App用户破千~ 小小成就 撒花

      现在正与同学合作开发第二款游戏,所以自己的经历差不多就是这样啦。

      至于收入问题,自己做的App和游戏一直都是完全免费的。上周开始尝试过每个App收取0.99美元。一周下来收入大概是6.几美元吧。人民币不到40元。很微薄,很微薄,很微薄!但毕竟刚刚起步~ 相信以后采用广告的方式以及做出更好的App,会有更有成效。

      一些建议:

      1. 选择平台很重要。我选择苹果的iOS平台是因为它学习成本较低,有非常易学的Swift语言以及成熟的开发环境。再者苹果设备专一,目标设备就那几款都数得出来。而且对于想获取收益的人来说也比较适合,只有一个应用市场。

      2. 学习的步骤大概是 有兴趣》选平台》学语言》学框架》实战开发!实战开发!实战开发!

      3. 自己一个人开始是很可能的,因为自己没有点基础也没人会跟你合作。后期的话一个小团队就很重要了。做一款App或者游戏工作量很大,而且还有美工,配乐等很多很多事情… 坑坑坑坑

      4. 英语很重要!英语很重要!英语很重要!

      一些干货,因为都是互联网的免费资源或者可以轻易购买到,不发链接啦

      1. 学习Swift语言,建议看CocoaChina翻译的苹果官方文档

      2. 学习完语言后要对开发App有一个基本概念~ 建议看这本小册子 - Become an Xcoder

      3. 一本APPCODA出的Swift开发应用的实例教程。整本书会带你开发一款完整的App。

      来了。手把手教你做一个吧。

      从零开始,手把手带你实现一个「专注睡前的 APP」。睡觉之前如果能有一个 APP,能让我们写一写这一天的见闻或者心得,同时又能看一会段子、瞄一会好看的妹子,放松一下疲惫的身心那该多好,这也是我完成这个 APP 的原因。APP 的全部代码我已经分享到 Github 上了,需要的直接 点击这里,如果喜欢的话,麻烦给个赞,谢谢啦。

      在开始写正文之前,先来一波效果的展示,看看五天过后我们能实现怎样的效果

      本次的教程分为 5 天,内容分别为:

      Day one,准备功能需求可行性分析Day two,UI 及公共类的封装界面的设计及实现公共类的实现Day three,日记模块日记的展示悬浮菜单的实现日记增删改的实现Day four,妹子模块图片的获取图片的展示详情页面的展示Day five,段子模块段子数据的获取段子的显示

      Day one

      俗话说,万事开头难,在开始敲代码之前,先让我们来做一些必要的准备,这样才能事半功倍嘛!

      既然要做一个 APP,那我们首先还是得把 APP 的功能都列出来,有了方向才能更好的努力,因为我想做的是一个专门给睡觉前用的 APP,所以我觉得应该有以下的这些功能

      1、日记的增删改2、显示一些有趣好玩的段子3、瀑布流展示漂亮的妹子4、保存日记的内容以及缓存妹子图片

      虽然说需求不多,但是却要运用到网络、数据存储、图片缓存、UI 设计等内容,相信整个 APP 完成下来,必定能巩固我们的 Android 基础。

      我们这个 APP 主要有三个模块,日记模块主要是运用到了数据库的知识,难度不大。但是,段子模块和妹子模块的数据要从哪来,这便是要好好考虑的了。幸好现在是个开源的时代,很多的数据,网上已经开源出来了。

      我们先来看一下数据的内容

      group:{

      text: "教授在河边,常常看到两只龟,缩着一动不动。有天忍不住好奇,问一农

      民:这两只乌龟在干吗?农民说:他们在pk。教授不解地问:动都没动过p什么

      k。老农说:他们在比谁寿命长。教授说:可是壳上有甲骨文的那只,早就死了埃

      这时,另一只猛然探出头来骂到:md,死了也不吭一声!有甲骨文的那只也伸

      出头来:“专家说啥你信啥1",

      user:{

      user_id: 4669064575,

      name: "馒头啊",

      avatar_url: "http://p3.pstatp.com/medium/6237/7969345239",

      },

      content: "教授在河边,常常看到两只龟,缩着一动不动。有天忍不住好奇,问

      一农民:这两只乌龟在干吗?农民说:他们在pk。教授不解地问:动都没动过

      p什么k。老农说:他们在比谁寿命长。教授说:可是壳上有甲骨文的那只,早

      就死了埃这时,另一只猛然探出头来骂到:md,死了也不吭一声!有甲骨文

      的那只也伸出头来:“专家说啥你信啥1",

      ...

      }

      {

      id: "56cc6d1d421aa95caa7076df",

      type: "福利",

      url: "http://ww1.sinaimg.cn/large/7a8aed7bgw1esxxi1vbq0j20qo0hstcu.jpg",

      used: true,

      who: "张涵宇"

      }

      上面那两段代码分别是段子和妹子模块的 json 类型的数据,我已经将一些没用的字段去掉了。剩下的都是我们想要的数据。可以看到段子数据中,有着段子的内容,以及发布者的头像和名字。而妹子数据中有着图片的 url、id、以及图片的类型。相信有了这么丰富的数据,我们想要完成这个 APP 也是有底气了。

      Day two

      既然我们想要完成一个好看的 APP,那么好看的界面便是必不可少的,这里我强烈推荐 APP 界面的设计必须尽量遵从 Google 提出的 Material Design,在这个推荐一个能够让我们实现 Material Design 变得更加简单的网站 material design palette,我这个 APP 的配色就是用这个网站完成的,贴几张图片,让你感受一下它的强大。

      借助这个网站便能让我们完成 APP 的配色以及图标的收集,为下一步功能的实现,先打好了基础,至于界面的设计就仁者见仁智者见智了,篇幅有限,我就不多讲了。

      APP 的最终设计效果如下:

      二、公共类的实现

      因为这个项目有三个模块,有一些东西其实是可以通用的,如果我们先把这些能够通用的东西,封装起来,供给所有的模块调用的话,相信会大大提高我们的开发效率。

      这个 APP 中,很多地方都要用到网络请求,因此也就很有必要将网络请求封装起来,因为这个 APP 的规模比较小,因此我选择了 Volley 这个网络框架作为我们网络请求库,把网络请求封装起来,哪个地方需要,调用一下就行了。对于网络请求,我觉得每个程序员都该懂点 HTTP,这里附上一篇有关 HTTP 的文章程序员都该懂点 HTTP。

      先让我们来写个将网络请求进行回调的接口

      public interface VolleyResponseCallback{

      void onSuccess(String response);

      void onError(VolleyError error);

      }

      然后将网络请求封装起来

      public class VolleyHelper{

      /**

      * 用于发送 Get 请求的封装方法

      *

      * @param context Activity 的实例

      * @param url 请求的地址

      * @param callback 用于网络回调的接口

      */

      public static void sendHttpGet(Context context, String url, final VolleyResponseCallback callback){

      RequestQueue requestQueue=Volley.newRequestQueue(context);

      StringRequest stringRequest=new StringRequest(url

      , new Response.Listener<String>(){

      @Override

      public void onResponse(String s){

      callback.onSuccess(s);

      }

      }, new Response.ErrorListener(){

      @Override

      public void onErrorResponse(VolleyError error){

      callback.onError(error);

      }

      });

      requestQueue.add(stringRequest);

      }

      }

      因为我们这个 APP 中,获取到的数据都是 Json 格式的,因此也就有必要将有关的 Json 解析封装成一个工具类,传入一个 String 类型的数据,直接得到数据实体类的 List。

      public class CommonParser{

      /**

      * 用来解析列表性的JSON数据

      * 如:

      *{"success":true,"fileList":[{"filename":"文件名1","fileSize":"文件大小1"},

      *{"filename":"文件名2","fileSize":"文件大小2"}]}

      *

      * @param result     网络返回来的JSON数据   比如:上面的整串数据

      * @param successKey 判断网络是否成功的字段  比如:上面的success字段

      * @param arrKey     列表的字段            比如:上面的fileList字段

      * @param clazz      需要解析成的Bean类型

      * @param <T>        需要解析成的Bean类型

      * @return

      */

      public static <T> List<T> parseForList(String result, String successKey, String arrKey, Class<T> clazz){

      List<T> list=new ArrayList<>();

      JSONObject rootJsonObject=null;

      try{

      rootJsonObject=new JSONObject(result);

      if (rootJsonObject.getBoolean(successKey)){

      JSONArray rootJsonArray=rootJsonObject.getJSONArray(arrKey);

      Gson g=new Gson();

      for (int i=0; i < rootJsonArray.length(); i++){

      T t=g.fromJson(rootJsonArray.getJSONObject(i).toString(), clazz);

      list.add(t);

      }

      }

      }catch (JSONException e){

      e.printStackTrace();

      }

      return list;

      }

      }

      主页面我用的是 TabLayout + ViewPager + Fragment,也是现在主流 APP 主页面的显示方式。主界面底部是我们三个模块的图标和名称,通过左右滑动能实现界面的跳转。

      public class CommonTabBean implements CustomTabEntity{

      private int selectedIcon;

      private int unselectedIcon;

      private String title;

      public CommonTabBean(String title){

      this.title=title;

      }

      public CommonTabBean(String title, int selectedIcon, int unselectedIcon){

      this.title=title;

      this.selectedIcon=selectedIcon;

      this.unselectedIcon=unselectedIcon;

      }

      @Override

      public String getTabTitle(){

      return title;

      }

      @Override

      public int getTabSelectedIcon(){

      return selectedIcon;

      }

      @Override

      public int getTabUnselectedIcon(){

      return unselectedIcon;

      }

      }

      public class CommonPagerAdapter extends FragmentPagerAdapter{

      private List<Fragment> mFragments;

      public CommonPagerAdapter(FragmentManager fragmentManager, List<Fragment> mFragments){

      super(fragmentManager);

      this.mFragments=mFragments;

      }

      @Override

      public Fragment getItem(int position){

      return mFragments.get(position);

      }

      @Override

      public int getCount(){

      return mFragments.size();

      }

      }

      Day three

      关于日记模块的实现,其实我是复用了以前写过的一个日记 APP,具体的思路和做法,可以参考我的这篇文章 Android 一款十分简洁、优雅的日记 APP

      Day four

      public class MeiziBean{

      @SerializedName("_id")

      private String id;

      @SerializedName("url")

      private String imageUrl;

      @SerializedName("who")

      private String who;

      public String getId(){

      return id;

      }

      public void setId(String id){

      this.id=id;

      }

      public String getImageUrl(){

      return imageUrl;

      }

      public MeiziBean(String imageUrl){

      this.imageUrl=imageUrl;

      }

      }

      可以看到我是用瀑布流的方式来实现图片的展示,效果还不错,但其实实现起来也是很简单的

      先写个图片的布局作为 RecyclerView 的 Item

      <android.support.v7.widget.CardView

      xmlns:android="http://schemas.android.com/apk/res/android"

      android:layout_width="match_parent"

      android:layout_height="wrap_content">

      <ImageView

      android:id="@+id/item_iv_meizi"

      android:layout_width="match_parent"

      android:layout_height="wrap_content"

      android:layout_centerHorizontal="true"

      android:layout_centerVertical="true"

      />

      </android.support.v7.widget.CardView>

      可以看到我在 ImageView 的外面加了一个 CardView,这个一种卡片式布局,能让图片看起来就像一张卡片一样,相当的优雅、美观。

      接着编写 Adapter,将数据和界面进行绑定

      public class MeiziAdapter extends RecyclerView.Adapter<MeiziAdapter.MeiziViewHolder>{

      private List<MeiziBean> mMeiziBeanList;

      private Fragment mFragment;

      public MeiziAdapter(List<MeiziBean> mMeiziBeanList, Fragment mFragment){

      this.mMeiziBeanList=mMeiziBeanList;

      this.mFragment=mFragment;

      }

      @Override

      public MeiziViewHolder onCreateViewHolder(ViewGroup parent, int viewType){

      View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.item_meizi, null);

      return new MeiziViewHolder(view);

      }

      @Override

      public void onBindViewHolder(MeiziViewHolder holder, final int position){

      Glide.with(mFragment)

      .load(mMeiziBeanList.get(position).getImageUrl())

      .fitCenter()

      .dontAnimate()

      .diskCacheStrategy(DiskCacheStrategy.ALL)

      .into(holder.mIvMeizi);

      holder.mIvMeizi.setOnClickListener(new View.OnClickListener(){

      @Override

      public void onClick(View v){

      ArrayList<String> resultList=new ArrayList<String>();

      for (MeiziBean meiziBean : mMeiziBeanList){

      resultList.add(meiziBean.getImageUrl());

      }

      DetailActivity.startActivity(mFragment.getActivity(), resultList, position);

      }

      });

      }

      @Override

      public int getItemCount(){

      if(mMeiziBeanList.size() > 0){

      return mMeiziBeanList.size();

      }

      return 0;

      }

      public static class MeiziViewHolder extends RecyclerView.ViewHolder{

      ImageView mIvMeizi;

      public MeiziViewHolder(View itemView){

      super(itemView);

      mIvMeizi=(ImageView) itemView.findViewById(R.id.item_iv_meizi);

      }

      }

      }

      最后在 Fragment 进行数据的获取,以及布局的初始化就行了

      public class MeiziFragment extends Fragment{

      ......

      @Nullable

      @Override

      public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState){

      View view=inflater.inflate(R.layout.fragment_meizi, container, false);

      ButterKnife.bind(this, view);

      initView();

      refreshMeizi();

      return view;

      }

      /**

      * 刷新当前界面

      */

      private void refreshMeizi(){

      mRefresh.setColorSchemeResources(R.color.colorPrimary);

      mRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener(){

      @Override

      public void onRefresh(){

      initView();

      mRefresh.setRefreshing(false);

      }

      });

      }

      private void initView(){

      VolleyHelper.sendHttpGet(getActivity(), MeiziApi.getMeiziApi(), new VolleyResponseCallback(){

      @Override

      public void onSuccess(String s){

      response=s;

      meiziBeanList=GsonHelper.getMeiziBean(response);

      mRvShowMeizi.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));

      Collections.shuffle(meiziBeanList);

      mRvShowMeizi.setAdapter(new MeiziAdapter(meiziBeanList, MeiziFragment.this));

      }

      @Override

      public void onError(VolleyError error){

      Logger.d(error);

      }

      });

      }

      干巴巴的,整个模块只能显示妹子的图片怎么行呢!!!怎么着也得能查看大图,根据手势放大缩小,以及浏览下一张图片才行嘛,说干就干。

      因为图片需要有根据手势来放大缩小的功能,因此我便想到了 PhotoView,这是网上一个大神写的,继承自 ImageView 的一个自定义控件。图片加载我用的是

      Glide,如果没了解过这个库的,强烈推荐,一行代码就能搞定图片加载,你确定不研究一下。

      public class DetailFragment extends Fragment{

      public static DetailFragment newInstance(String imageUrl){

      DetailFragment fragment=new DetailFragment();

      Bundle bundle=new Bundle();

      bundle.putString(IMAGE_URL, imageUrl);

      fragment.setArguments(bundle);

      return fragment;

      }

      @Nullable

      @Override

      public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState){

      View view=inflater.inflate(R.layout.fragment_detail, container, false);

      ButterKnife.bind(this, view);

      Bundle bundle=getArguments();

      String imageUrl=bundle.getString(IMAGE_URL);

      Glide.with(this).load(imageUrl).into(mPvShowPhoto);

      mPvShowPhoto.setOnPhotoTapListener(new PhotoViewAttacher.OnPhotoTapListener(){

      @Override

      public void onPhotoTap(View view, float v, float v1){

      getActivity().finish();

      }

      @Override

      public void onOutsidePhotoTap(){

      }

      });

      return view;

      }

      }

      Day five

      段子数据的获取其实跟妹子模块的方法基本一样

      先编写实体类

      public class DuanziBean{

      @SerializedName("group")

      private GroupBean groupBean;

      private String type;

      public GroupBean getGroupBean(){

      return groupBean;

      }

      public void setGroupBean(GroupBean groupBean){

      this.groupBean=groupBean;

      }

      public String getType(){

      return type;

      }

      public void setType(String type){

      this.type=type;

      }

      }

      public class GroupBean{

      private String text;

      private long id;

      private UserBean user;

      public String getText(){

      return text;

      }

      public long getId(){

      return id;

      }

      public UserBean getUser(){

      return user;

      }

      public static class UserBean{

      private long user_id;

      private String name;

      private String avatar_url;

      public String getName(){

      return name;

      }

      public String getAvatar_url(){

      return avatar_url;

      }

      }

      }

      写好实体类之后,使用我们之前已经封装好的网络请求工具以及解析工具,便能将返回的数据,解析成一个包含段子实体类的 List。

      老规矩,先写个 RecyclerView 的 Item

      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

      android:layout_width="match_parent"

      android:layout_height="match_parent"

      androidrientation="vertical"

      >

      <LinearLayout

      android:layout_width="match_parent"

      android:layout_height="40dp"

      android:paddingLeft="8dp"

      >

      <de.hdodenhof.circleimageview.CircleImageView

      android:id="@+id/duanzi_civ_avatar"

      android:layout_width="24dp"

      android:layout_height="24dp"

      android:src=https://www.zhihu.com/question/"@drawable/avatar"

      android:layout_gravity="center"

      />

      <TextView

      android:id="@+id/duanzi_tv_author"

      android:paddingLeft="8dp"

      android:paddingStart="8dp"

      android:layout_width="match_parent"

      android:layout_height="16dp"

      android:text="DeveloperHaoz"

      android:layout_gravity="center_vertical"

      />

      </LinearLayout>

      <TextView

      android:id="@+id/duanzi_tv_content"

      android:layout_width="match_parent"

      android:layout_height="wrap_content"

      android:paddingBottom="10dp"

      android:paddingLeft="40dp"

      android:paddingRight="10dp"

      android:text=""

      />

      <include layout="@layout/layout_app_divide"/>

      </LinearLayout>

      然后编写将数据和界面进行绑定的 Adapter

      public class DuanziAdapter extends RecyclerView.Adapter<DuanziAdapter.DuanziViewHolder>{

      private Fragment mFragment;

      private List<DuanziBean> mDuanziBeanList;

      public DuanziAdapter(Fragment fragment, List<DuanziBean> duanziBeanList){

      this.mFragment=fragment;

      this.mDuanziBeanList=duanziBeanList;

      }

      @Override

      public DuanziViewHolder onCreateViewHolder(ViewGroup parent, int viewType){

      View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.item_duanzi, null);

      return new DuanziViewHolder(view);

      }

      @Override

      public void onBindViewHolder(DuanziViewHolder holder, int position){

      try{

      DuanziBean duanziBean=mDuanziBeanList.get(position);

      Glide.with(mFragment).load(duanziBean.getGroupBean().getUser().getAvatar_url()).into(holder.mCivAvatar);

      holder.mTvContent.setText(duanziBean.getGroupBean().getText());

      holder.mTvAuthor.setText(duanziBean.getGroupBean().getUser().getName());

      }catch (Exception e){

      e.printStackTrace();

      }

      }

      @Override

      public int getItemCount(){

      return mDuanziBeanList.size();

      }

      public static class DuanziViewHolder extends RecyclerView.ViewHolder{

      private CircleImageView mCivAvatar;

      private TextView mTvAuthor;

      private TextView mTvContent;

      public DuanziViewHolder(View itemView){

      super(itemView);

      mCivAvatar=(CircleImageView) itemView.findViewById(R.id.duanzi_civ_avatar);

      mTvAuthor=(TextView) itemView.findViewById(R.id.duanzi_tv_author);

      mTvContent=(TextView) itemView.findViewById(R.id.duanzi_tv_content);

      }

      }

      }

      最后段子页面中进行数据和获取以及界面的初始化

      public class DuanziFragment extends Fragment{

      @Nullable

      @Override

      public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState){

      View view=inflater.inflate(R.layout.fragment_duanzi, container, false);

      ButterKnife.bind(this, view);

      initView();

      initRefresh();

      return view;

      }

      private void initRefresh(){

      mRefresh.setColorSchemeResources(R.color.colorPrimary);

      mRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener(){

      @Override

      public void onRefresh(){

      initView();

      mRefresh.setRefreshing(false);

      }

      });

      }

      private void initView(){

      VolleyHelper.sendHttpGet(getActivity(), DuanziApi.GET_DUANZI, new VolleyResponseCallback(){

      @Override

      public void onSuccess(String response){

      List<DuanziBean> mDuanziBeanList=GsonHelper.getDuanziBeanList(response);

      mDuanziBeanList.remove(3);

      mRvShowDuanzi.setLayoutManager(new LinearLayoutManager(getActivity()));

      mRvShowDuanzi.setAdapter(new DuanziAdapter(DuanziFragment.this, mDuanziBeanList));

      }

      @Override

      public void onError(VolleyError error){

      Logger.d(error);

      }

      });

      }

      }

      作者:developerHaoz

      来源:慕课网

      本文原创发布于慕课网 ,转载请注明出处,谢谢合作
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏 分享分享
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    快速回复 返回顶部 返回列表