Android小项目 |
您所在的位置:网站首页 › 今日头条新闻发布流程图表下载 › Android小项目 |
前言:
在公司学习了一段时间Android知识,决定做一个小项目,目的是学会运用所学的基础知识,在这里记录一下开发历程,大家可以把它看成一款入门级练手的 Demo 应用吧~ 项目概述: 类型:新闻APP(低仿今日头条) 基本功能:欢迎页面加载(3s,点击可跳过)——Activity相关 用户注册/登录 ——SQLite运用 横向滑动列表显示新闻类别——TabLayout、ViewPager、FragmentPagerAdapter的应用 底部菜单栏 切换——Fragment运用 –-主页(显示新闻列表)——ListView –-设置(退出应用、退出登录、清空缓存)——Activity管理、SharePreference –-我的(账号安全、新闻收藏夹)——SQLite 新闻列表下拉、上滑实现刷新——自定义ListView 逐条收藏新闻、删除新闻——SharePreference 仿UI界面——各类控件运用 点击查看新闻详情 —— WebView 用户界面更换头像功能——Android运行时权限、多媒体、Content Provider 源码及下载地址: https://download.csdn.net/download/qq_34149526/10977199 PS: 最近有不少朋友反馈程序闪退问题,我查看了一下,是因为我采用的数据接口(天行数据)请求下来的数据中,图片链接的数据为空(之前一直是正常数据),导致解析图片时空指针异常,最终程序闪退。json数据如下: { "code": 200, "msg": "success", "newslist": [{ "ctime": "2019-07-18 00:00", "title": "空间科学卫星:迈向空间科学强国", "description": "新华科技", "picUrl": "",--------------->此处为空!!! "url": "http:\/\/www.xinhuanet.com\/tech\/2019-07\/18\/c_1124767044.htm" }, …… }修改方法一:替换含有图片数据接口,可采用聚合数据等(使用自行百度),注意修改代码中的数据字段名!!! 修改方法二:在代码中解析图片的地方加上非空校验,程序不会崩溃,但是新闻列表中不会显示图片。 修改方法三:自行想办法解决~哈哈 下面简单贴一下修改方法二: //针对以下几个类做非空校验,希望大家以我为鉴,养成良好编码习惯。 1、MyBitmapUtils.java: public Bitmap getBitmap(String url) { if(TextUtils.isEmpty(url)){ return null; } Bitmap bitmap; …… } 2、HttpUtils.java: public static Bitmap decodeUriAsBitmapFromNet(String imgUrl) { if(TextUtils.isEmpty(imgUrl)){ return null; } URL fileUrl = null; Bitmap bitmap = null; …… } 3、NewsAdapter.java: @Override public View getView(int position, View convertView, ViewGroup parent) { …… if(news.getNews_img()!=null) { viewHolder.newsImg.setImageBitmap(news.getNews_img()); } …… } 4、NetCacheUtils.java: private Bitmap downLoadBitmap(String url) { …… } finally { if (conn != null) { conn.disconnect(); } } …… }关于界面无法显示内容,原因是我申请的天行数据的API接口调用次数已用完(当时我是有10万次的免费调用次数,不得不感叹你们的强大),具体解决方法是更换TechFragment、MiliFragment、SportFragment、EnteFragment四个类里面的url地址,原地址已无法正常请求数据。可以自己去天行数据申请免费接口,替换即可(具体url格式参考天行数据官方文档,我已经很久不用它了)。 在这里我再贴出一位评论区小伙伴 “qq_41835735”给出的解决方案,给大家一个参考。 ----------华丽分割线---------- 下面进入重点,开始介绍这个项目 1、项目结构: 类文件: 资源文件: 2、主要功能及其代码实现: 欢迎页面:(持续时间为3s);利用handler机制并开启一个线程,实现展示欢迎页面3s后页面跳转; final Message message = new Message(); final Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); message.what = 1; handler.sendMessage(message); } catch (InterruptedException e) { e.printStackTrace(); } } });欢迎页面加载完毕后会判断是否有用户登录,若没有用户登录,会跳转到注册&登录页面: final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 1) { //判断用户是否登录 boolean userIsLogin = (boolean) SharedPreUtil.getParam(WelcomeActivity.this, SharedPreUtil.IS_LOGIN, false); if (userIsLogin) { Intent intent = new Intent(WelcomeActivity.this, MainActivity.class); startActivity(intent); } else { Intent intent = new Intent(WelcomeActivity.this, LoginOrRegisterActivity.class); startActivity(intent); } finish(); } else if (msg.what == 0) { thread.interrupt(); } } }; 注册或登录页面:
“注册”和“登陆”功能的实现主要是应用的SQLite数据库存储技术; 注册——存: SQLiteDatabase db = dbHelper.getWritableDatabase(); String username_str = username.getText().toString(); String userpassword_str = userpassword.getText().toString(); String repassword_str = repassword.getText().toString(); if (userpassword_str.equals(repassword_str)) { ContentValues values = new ContentValues(); //组装数据 values.put("name", username_str); values.put("password", userpassword_str); db.insert("User", null, values); startActivity(new Intent(RegisterActivity.this, LoginActivity.class)); finish(); }登陆——取: if (cursor.moveToFirst()) { String userpassword_db = cursor.getString(cursor.getColumnIndex("password")); if (userpassword_str.equals(userpassword_db)) { SharedPreUtil.setParam(LoginActivity.this, SharedPreUtil.IS_LOGIN, true); SharedPreUtil.setParam(LoginActivity.this, SharedPreUtil.LOGIN_DATA, username_str); //user.setUsername(username_str); //user.setPassword(userpassword_str); Intent intent = new Intent(LoginActivity.this, MainActivity.class); TimeCount.getInstance().setTime(System.currentTimeMillis()); startActivity(intent); finish(); } else { Toast.makeText(LoginActivity.this, "密码错误,请重新登录", Toast.LENGTH_SHORT).show(); } }另外在实现“注册”中上传头像功能时涉及到一个小知识点——运行时权限;因为要访问图库: 若用户不选择上传,程序会默认使用一个给定好的图片作为用户头像。 @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openAlbum(); } else { Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show(); } break; } } private void openAlbum() { Intent intent = new Intent("android.intent.action.GET_CONTENT"); intent.setType("image/*"); startActivityForResult(intent, CHOSSE_PHOTO); } @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case CHOSSE_PHOTO: if (resultCode == -1) { String imgPath = AlbumUtil.handleImageOnKitKat(this, data); setHead(imgPath); } break; default: break; } }用户注册登陆后,即进入主页面:默认为科技新闻,左右滑动即可切换新闻类别; 新闻列表的显示原理及ListView、数据源与Adapter三者相结合,呈现出该画面; final String url = "http://api.tianapi.com/keji/?key=7d829a4176fef4ad7409c2dc129905ed&num=30"; private View view; private LoadListView mListView; private List newsList; private NewsAdapter adapter;其中数据源的获取涉及到了HTTPClient的GET请求网络资源以及解析json数据的相关知识; JSONObject jsonObject = new JSONObject(jsonData); JSONArray jsonArray = jsonObject.getJSONArray("newslist"); for (int i = 0; i < 10; i++) { JSONObject json_news = jsonArray.getJSONObject(i); String imgUrl = json_news.getString("picUrl"); /** * 采取三级缓存策略加载图片 */ Bitmap bitmap = myBitmapUtils.getBitmap(imgUrl); String title = json_news.getString("title"); String date = json_news.getString("ctime"); String author_name = json_news.getString("description"); String url = json_news.getString("url"); Log.d(TAG, "url:*-*-*-*-*-*-*" + imgUrl); News news = new News(bitmap, title, url, imgUrl, date, author_name); newsList.add(news); getActivity().runOnUiThread(new Runnable() { @Override public void run() { adapter.notifyDataSetChanged(); } });该页面同样实现了下拉&上滑刷新新闻的功能: 下拉刷新: 上滑加载: 该功能具体实现请参考——ListView实现上拉加载&下拉刷新; 删除新闻:在新闻列表点击每条新闻中的×号可完成新闻删除。 @Override public void click(View view) { Toast.makeText(getContext(), "该新闻已删除!", Toast.LENGTH_SHORT).show(); newsList.remove(Integer.parseInt(view.getTag().toString())); adapter.notifyDataSetChanged(); } 为每一个ListView item设置一个鼠标监听器,在remove方法中传入item的索引位置即可完成新闻列表的删除。 新闻详情页面:(及加载提示)
代码实现: show_news.getSettings().setJavaScriptEnabled(true); Intent intent = getIntent(); final String news_url = intent.getStringExtra("url"); final String news_title = intent.getStringExtra("title"); final String news_date = intent.getStringExtra("date"); final String news_author = intent.getStringExtra("author"); final String news_picurl = intent.getStringExtra("pic_url"); show_news.loadUrl(news_url);
获得intent对象中由上一个页面传来的新闻URL,将WebView初始化后进行加载;完成页面详情的展示。 mDialog = new ProgressDialog(ShowNewsActivity.this); mDialog.setMessage("玩命加载ing"); show_news.setWebViewClient(new WebViewClient() { //网页加载时的回调 @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); if (!mDialog.isShowing()) { mDialog.show(); } } //网页停止加载时的回调 @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // 如果没有显示,则显示 if (mDialog.isShowing()) mDialog.dismiss(); } });实例化ProgressDialog对象,设置标题与提示信息,以对用户进行友好提示。 新闻收藏:
代码实现: 主要是利用SQLite存储整个收藏新闻信息; collect_news.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { collect_news.setImageResource(R.drawable.favorite_selected); SQLiteDatabase db = helper.getWritableDatabase(); ContentValues values = new ContentValues(); //组装数据 values.put("news_url", news_url); values.put("news_title", news_title); values.put("news_date", news_date); values.put("news_author", news_author); values.put("news_picurl", news_picurl); db.insert("Collection_News", null, values); db.close(); 设置页面: 清空缓存:因为在加载新闻列表时对新闻图片采取了三级缓存策略(网络,本地文件,内存),所以会产生一定的缓存,该功能会清理掉所有缓存; 代码实现: // 获取文件 //Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据 //Context.getExternalCacheDir() --> SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据 public static long getFolderSize(File file) throws Exception { long size = 0; try { File[] fileList = file.listFiles(); for (int i = 0; i < fileList.length; i++) { // 如果下面还有文件 if (fileList[i].isDirectory()) { size = size + getFolderSize(fileList[i]); } else { size = size + fileList[i].length(); } } } catch (Exception e) { e.printStackTrace(); } return size; } /** * * 清除本应用内部缓存(/data/data/com.xxx.xxx/cache) * * * * @param context */ public static void cleanInternalCache(Context context) { deleteFilesByDirectory(context.getCacheDir()); } 退出应用:用户点击后直接退出程序并返回桌面。 代码实现: 专门创建了一个用户维护所有活动(页面)的工具类,当用户点击退出按钮时实际调用exit方法,结束活动类表中每一个活动,并执行System.exit(0);退出。 public class ApplicationUtil extends Application { private List mList = new LinkedList(); private static ApplicationUtil instance; private ApplicationUtil() { } public synchronized static ApplicationUtil getInstance() { if (instance == null) { instance = new ApplicationUtil(); } return instance; } // 添加Activity到列表中维持 public void addActivity(Activity activity) { mList.add(activity); } public void exit() { try { for (Activity activity : mList) { if (activity != null) { activity.finish(); } } } catch (Exception e) { e.printStackTrace(); } finally { System.exit(0); } } } 我的: 账号安全:该功能即修改用户基本信息;本质是对SQLite的应用; 收藏夹:列举曾收藏过的所有新闻;
----------华丽分割线----------
至此,项目介绍完毕。
|
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |