Android App开发实战项目之给用户推荐旅游信息图片(附源码 简单易懂)

简介: Android App开发实战项目之给用户推荐旅游信息图片(附源码 简单易懂)

需要全部源码请点赞关注收藏后评论区留言~~~

一、需求描述

假定用户打开一个旅游App想看看哪里风景比较优美,那么App应当展示各地的风景名声图片,为了让界面不太呆板,可以考虑交错显示风景图片,接着用户向下拉动页面,想要刷新界面浏览更多的图片,此时App界面响应下拉刷新手势弹出加载源泉

等待App努力加载新的图片列表,加载完成之后,界面展示新一批的风景图片,同时加载圆圈消失。

接下来我们实践如何让App从服务端获取随机推荐的风景图片

二、界面设计

界面设计比较简单 主要用到了以下控件

1:循环视图RecyclerView的瀑布流布局

2:下拉刷新布局 SwipeRefreshLayout

界面简单,但是背后设计的网络技术比较复杂 主要用到了以下及几种技术

1:HTTP接口调用 App向后端服务器请求风景图片列表

2:JSON格式 App与服务器之间的数据交互

3:异步任务AsyncTask 访问HTTP接口耗时,需要放在专门的异步任务之中

4:图片加载框架Glide 加载网络图片并显示在界面上  

效果如下 此处建议连接真机测试 模拟机不好与后端网络交互

三、关键部分

1:循环视图的首次加载与重新加载

2:原始网络图片的加载

3:不同部分源码之间关系

1:GuessLikeActivity 风景列表的活动代码 主类

2:PhotoRecyclerAdapter 风景图片的适配器代码

3:PhotoDetailActivity  图片详情的活动代码

4:GetPhotoTask 获取网络图片的任务代码 通过调用HTTP接口,从后端服务器获得JSON格式的风景图片信息列表

5:服务端工程的GetPhoto 图片获取接口的服务端代码

四、代码

GuessLikeActivity

package com.example.chapter14;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener;
import com.example.chapter14.adapter.PhotoRecyclerAdapter;
import com.example.chapter14.bean.PhotoInfo;
import com.example.chapter14.task.resp.GetPhotoResp;
import com.example.chapter14.task.GetPhotoTask;
import com.example.chapter14.widget.SpacesDecoration;
import com.google.gson.Gson;
import java.util.List;
@SuppressLint("DefaultLocale")
public class GuessLikeActivity extends AppCompatActivity implements View.OnClickListener, OnRefreshListener, GetPhotoTask.GetPhotoListener {
    private final static String TAG = "GuessLikeActivity";
    private SwipeRefreshLayout srl_like; // 声明一个下拉刷新布局对象
    private RecyclerView rv_like; // 声明一个循环视图对象
    private PhotoRecyclerAdapter mAdapter; // 声明一个线性适配器对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_guess_like);
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("大美河山");
        findViewById(R.id.iv_back).setOnClickListener(this);
        initRecyclerView(); // 初始化瀑布流布局的循环视图
    }
    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.iv_back) {
            finish(); // 关闭当前页面
        }
    }
    // 初始化瀑布流布局的循环视图
    private void initRecyclerView() {
        rv_like = findViewById(R.id.rv_like); // 从布局文件中获取名叫rv_like的循环视图
        // 创建一个垂直方向的瀑布流网格布局管理器
        StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2, RecyclerView.VERTICAL);
        rv_like.setLayoutManager(manager); // 设置循环视图的布局管理器
        rv_like.addItemDecoration(new SpacesDecoration(1)); // 设置循环视图的空白装饰
        srl_like = findViewById(R.id.srl_like); // 从布局文件中获取名叫srl_like的下拉刷新布局
        srl_like.setOnRefreshListener(this); // 设置下拉刷新布局的下拉刷新监听器
        // 设置下拉刷新布局的进度圆圈颜色
        srl_like.setColorSchemeResources(R.color.red, R.color.orange, R.color.green, R.color.blue);
        srl_like.setRefreshing(true); // 设置状态为正在刷新,此时会弹出进度圆圈
        onRefresh(); // 执行刷新动作
    }
    // 一旦在下拉刷新布局内部往下拉动页面,就触发下拉监听器的onRefresh方法
    @Override
    public void onRefresh() {
        GetPhotoTask task = new GetPhotoTask(); // 创建一个获取照片的异步任务
        task.setGetPhotoListener(this); // 设置照片获取的监听器
        task.execute(); // 把照片获取任务加入到处理队列
    }
    // 在获得照片列表信息后触发
    @Override
    public void onGetPhoto(String resp) {
        srl_like.setRefreshing(false); // 设置状态为正在刷新,此时会关闭进度圆圈
        // 把JSON串转换为对应结构的实体对象
        GetPhotoResp photoResp = new Gson().fromJson(resp, GetPhotoResp.class);
        if (photoResp == null) { // 未获得返回报文,说明HTTP调用失败
            return;
        }
        List<PhotoInfo> photo_list = photoResp.getPhotoList();
        if (photo_list!=null && photo_list.size()>0) {
            Log.d(TAG, "photo_list.size()="+photo_list.size());
            if (mAdapter == null) { // 首次加载前不存在适配器
                // 构建一个照片列表的瀑布流网格适配器
                mAdapter = new PhotoRecyclerAdapter(this, photo_list);
                mAdapter.setOnItemClickListener(mAdapter); // 设置照片列表的点击监听器
                rv_like.setAdapter(mAdapter); // 设置循环视图的瀑布流网格适配器
            } else { // 再次加载时已经存在适配器了
                mAdapter.setPhotoList(photo_list);
                mAdapter.notifyDataSetChanged(); // 通知适配器发生了数据变更
            }
            rv_like.scrollToPosition(0); // 让循环视图滚动到第一项所在的位置
        }
    }
}

PhotoRecyclerAdapter

package com.example.chapter14.adapter;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import com.example.chapter14.PhotoDetailActivity;
import com.example.chapter14.R;
import com.example.chapter14.bean.PhotoInfo;
import com.example.chapter14.util.Utils;
import com.example.chapter14.widget.RecyclerExtras.OnItemClickListener;
import java.util.List;
import java.util.Random;
public class PhotoRecyclerAdapter extends RecyclerView.Adapter<ViewHolder> implements OnItemClickListener {
    private final static String TAG = "PhotoRecyclerAdapter";
    private Context mContext; // 声明一个上下文对象
    private List<PhotoInfo> mPhotoList; // 照片列表
    public PhotoRecyclerAdapter(Context context, List<PhotoInfo> photoList) {
        mContext = context;
        mPhotoList = photoList;
    }
    public void setPhotoList(List<PhotoInfo> photoList) {
        mPhotoList = photoList;
    }
    // 获取列表项的个数
    public int getItemCount() {
        return mPhotoList.size();
    }
    // 创建列表项的视图持有者
    public ViewHolder onCreateViewHolder(ViewGroup vg, int viewType) {
        // 根据布局文件item_photo.xml生成视图对象
        View v = LayoutInflater.from(mContext).inflate(R.layout.item_photo, vg, false);
        return new ItemHolder(v);
    }
    // 绑定列表项的视图持有者
    public void onBindViewHolder(ViewHolder vh, final int position) {
        ItemHolder holder = (ItemHolder) vh;
        PhotoInfo photo = mPhotoList.get(position);
        ViewGroup.LayoutParams params = holder.ll_item.getLayoutParams();
        params.height = 150 + new Random().nextInt(100); // 生成随机高度,从而呈现瀑布流效果
        params.height = Utils.dip2px(mContext, params.height);
        holder.ll_item.setLayoutParams(params);
        holder.tv_title.setText(photo.title);
        // 利用Glide加载网络图片,并在图像视图上显示
        Glide.with(mContext).load(photo.image_url)
                .transition(DrawableTransitionOptions.withCrossFade(1000)) // 设置时长1秒的渐变动画
                .into(holder.iv_pic);
        // 列表项的点击事件需要自己实现
        holder.ll_item.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickListener != null) {
                    mOnItemClickListener.onItemClick(v, position);
                }
            }
        });
    }
    // 获取列表项的类型
    public int getItemViewType(int position) {
        return 0;
    }
    // 获取列表项的编号
    public long getItemId(int position) {
        return position;
    }
    // 定义列表项的视图持有者
    public class ItemHolder extends ViewHolder {
        public LinearLayout ll_item; // 声明列表项的线性布局
        public ImageView iv_pic; // 声明一个照片的图像视图
        public TextView tv_title; // 声明一个标题的文本视图
        public ItemHolder(View v) {
            super(v);
            ll_item = v.findViewById(R.id.ll_item);
            iv_pic = v.findViewById(R.id.iv_pic);
            tv_title = v.findViewById(R.id.tv_title);
        }
    }
    // 声明列表项的点击监听器对象
    private OnItemClickListener mOnItemClickListener;
    public void setOnItemClickListener(OnItemClickListener listener) {
        this.mOnItemClickListener = listener;
    }
    // 处理列表项的点击事件
    public void onItemClick(View view, int position) {
        PhotoInfo photo = mPhotoList.get(position);
        // 以下跳到照片详情页面
        Intent intent = new Intent(mContext, PhotoDetailActivity.class);
        intent.putExtra("title", photo.title);
        intent.putExtra("image_url", photo.image_url);
        mContext.startActivity(intent); // 打开照片详情页面
    }
}

PhotoDetailActivity

package com.example.chapter14;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
public class PhotoDetailActivity extends AppCompatActivity implements View.OnClickListener {
    private final static String TAG = "PhotoDetailActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_photo_detail);
        String title = getIntent().getStringExtra("title");
        String image_url = getIntent().getStringExtra("image_url");
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText(title);
        findViewById(R.id.iv_back).setOnClickListener(this);
        ImageView iv_photo = findViewById(R.id.iv_photo);
        // 构建一个加载网络图片的建造器
        RequestBuilder<Drawable> builder = Glide.with(this).load(image_url)
                .transition(DrawableTransitionOptions.withCrossFade(1000)); // 设置时长1秒的渐变动画
        RequestOptions options = new RequestOptions(); // 创建Glide的请求选项
        options.override(Target.SIZE_ORIGINAL); // 展示原始图片
        options.disallowHardwareConfig(); // 关闭硬件加速,防止过大尺寸的图片加载报错
        // 在图像视图上展示网络图片。apply方法表示启用指定的请求选项
        builder.apply(options).into(iv_photo);
    }
    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.iv_back) {
            finish(); // 关闭当前页面
        }
    }
}

XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <include layout="@layout/title_tour" />
    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/srl_like"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_like"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#eeeeee" />
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>

创作不易 觉得有帮助请 点赞关注收藏~~~

相关文章
|
9月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
176 0
|
5月前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
6月前
|
存储 Java PHP
轻量化短视频电商直播带货APP源码全解析:核心功能与设计流程​
在电商直播热潮下,开发专属直播带货APP成为抢占市场关键。本文详解原生开发轻量化APP的核心功能与全流程设计,涵盖用户登录、商品浏览、直播互动、购物车、订单及售后功能,并介绍安卓端Java、苹果端Object-C、后台PHP的技术实现,助力打造高效优质的直播电商平台。
|
8月前
|
消息中间件 缓存 小程序
婚恋交友相亲公众号app小程序系统源码「脱单神器」婚恋平台全套代码 - 支持快速二次开发
这是一套基于SpringBoot + Vue3开发的婚恋交友系统,支持微信公众号、Uniapp小程序和APP端。系统包含实名认证、智能匹配、视频相亲、会员体系等功能,适用于婚恋社交平台和相亲交友应用。后端采用SpringBoot 3.x与MyBatis-Plus,前端使用Vue3与Uniapp,支持快速部署和二次开发。适合技术团队或有经验的个人创业者使用。
568 8
|
7月前
|
小程序 Java 关系型数据库
圈子系统公众号app小程序系统源码圈子系统带即时通讯 多级圈子系统源码 兴趣小组系统开源 私密圈子系统代码 会员制社区系统
本圈子系统解决方案提供即时通讯、多级圈子、兴趣小组、私密社区及会员制管理功能。支持开源与商业方案,推荐ThinkSNS+、EasyClub及OpenFire等系统,并提供前后端技术选型建议,助力快速搭建社交平台。
421 0
不封号的外卖抢单神器,美团抢单辅助器app,autojs版本源码
这个代码提供了基础框架,包含主循环、订单检测和点击功能。实际使用时需要根据美团骑手AP
|
9月前
|
XML 搜索推荐 Android开发
Android改变进度条控件progressbar的样式(根据源码修改)
本文介绍了如何基于Android源码自定义ProgressBar样式。首先分析了系统源码中ProgressBar样式的定义,发现其依赖一张旋转图片实现动画效果。接着分两步指导开发者实现自定义:1) 模仿源码创建一个旋转动画XML文件(放置在drawable文件夹),修改图片为自定义样式;2) 在UI控件中通过`indeterminateDrawable`属性应用该动画。最终实现简单且个性化的ProgressBar效果,附带效果图展示。
599 2
|
10月前
|
NoSQL 应用服务中间件 PHP
布谷一对一直播源码android版环境配置流程及功能明细
部署需基于 CentOS 7.9 系统,硬盘不低于 40G,使用宝塔面板安装环境,包括 PHP 7.3(含 Redis、Fileinfo 扩展)、Nginx、MySQL 5.6、Redis 和最新 Composer。Swoole 扩展需按步骤配置。2021.08.05 后部署需将站点目录设为 public 并用 ThinkPHP 伪静态。开发环境建议 Windows 操作系统与最新 Android Studio,基础配置涉及 APP 名称修改、接口域名更换、包名调整及第三方登录分享(如 QQ、微信)的配置,同时需完成阿里云与腾讯云相关设置。
|
11月前
|
存储 文件存储 Android开发
仿第八区APP分发下载打包封装系统源码
该系统为仿第八区APP分发下载打包封装系统源码,支持安卓、iOS及EXE程序分发,自动判断并稳定安装。智能提取应用信息,自动生成PLIST文件和图标,提供合理的点数扣除机制。支持企业签名在线提交、专属下载页面生成、云端存储(阿里云、七牛云),并优化签名流程,支持中文包及合并分发,确保高效稳定的下载体验。 [点击查看源码](https://download.csdn.net/download/huayula/90463452)
680 22

热门文章

最新文章