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>

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

相关文章
|
7天前
|
JSON 缓存 前端开发
HarmonyOS NEXT 5.0鸿蒙开发一套影院APP(附带源码)
本项目基于HarmonyOS NEXT 5.0开发了一款影院应用程序,主要实现了电影和影院信息的展示功能。应用包括首页、电影列表、影院列表等模块。首页包含轮播图与正在热映及即将上映的电影切换显示;电影列表模块通过API获取电影数据并以网格形式展示,用户可以查看电影详情;影院列表则允许用户选择城市后查看对应影院信息,并支持城市选择弹窗。此外,项目中还集成了Axios用于网络请求,并进行了二次封装以简化接口调用流程,同时添加了请求和响应拦截器来处理通用逻辑。整体代码结构清晰,使用了组件化开发方式,便于维护和扩展。 该简介概括了提供的内容,但请注意实际开发中还需考虑UI优化、性能提升等方面的工作。
52 11
|
4天前
|
前端开发 算法 安全
一站式搭建相亲交友APP丨交友系统源码丨语音视频聊天社交软件平台系统丨开发流程步骤
本文详细介绍了一站式搭建相亲交友APP的开发流程,涵盖需求分析、技术选型、系统设计、编码实现、测试、部署上线及后期维护等环节。通过市场调研明确平台定位与功能需求,选择适合的技术栈(如React、Node.js、MySQL等),设计系统架构和数据库结构,开发核心功能如用户注册、匹配算法、音视频聊天等,并进行严格的测试和优化,确保系统的稳定性和安全性。最终,通过云服务部署上线,并持续维护和迭代,提供一个功能完善、安全可靠的社交平台。
58 6
|
7天前
|
移动开发 小程序 前端开发
使用php开发圈子系统特点,如何获取圈子系统源码,社交圈子运营以及圈子系统的功能特点,圈子系统,允许二开,免费源码,APP 小程序 H5
开发一个圈子系统(也称为社交网络或社群系统)可以是一个复杂但非常有趣的项目。以下是一些关键特点和步骤,帮助你理解如何开发、获取源码以及运营一个圈子系统。
59 3
|
4天前
|
前端开发 搜索推荐 PHP
大开眼界!uniapp秀操作,陪玩系统新功能,陪玩app源码,可实时互动随心优化!
多客游戏陪玩系统采用前端uniapp与PHP语言,实现全开源、易改造,RTC传输协议确保低延迟语音连麦,分布式部署应对高并发。功能创新包括游戏约单、多人语音聊天室、动态广场、私信聊天等,提供高端社交和个性化服务,满足各类需求,让玩家畅享游戏乐趣。
|
7天前
|
小程序 安全 网络安全
清晰易懂!陪玩系统源码搭建的核心功能,陪玩小程序、陪玩app的搭建步骤!
陪玩系统源码包含多种约单方式、实时语音互动、直播间与聊天室、大神申请与抢单、动态互动与社交及在线支付与评价等核心功能。搭建步骤包括环境准备、源码上传与解压、数据库配置、域名与SSL证书绑定、伪静态配置及后台管理。注意事项涵盖源码安全性、二次开发、合规性和技术支持。确保平台安全、合规并提供良好用户体验是关键。
|
2月前
|
JSON 小程序 JavaScript
uni-app开发微信小程序的报错[渲染层错误]排查及解决
uni-app开发微信小程序的报错[渲染层错误]排查及解决
752 7
|
2月前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
775 1
|
5天前
|
前端开发 数据库 UED
uniapp开发,前后端分离的陪玩系统优势,陪玩app功能特点,线上聊天线下陪玩,只要4800
前后端分离的陪玩系统将前端(用户界面)和后端(服务器逻辑)分开开发,前者负责页面渲染与用户交互,后者处理数据并提供接口。该架构提高开发效率、优化用户体验、增强可扩展性和稳定性,降低维护成本,提升安全性。玩家可发布陪玩需求,陪玩人员发布服务信息,支持在线聊天、预约及线下陪玩功能,满足多样化需求。[演示链接](https://www.51duoke.cn/games/?id=7)
|
11天前
|
供应链 搜索推荐 API
1688APP原数据API接口的开发、应用与收益(一篇文章全明白)
1688作为全球知名的B2B电商平台,通过开放的原数据API接口,为开发者提供了丰富的数据资源,涵盖商品信息、交易数据、店铺信息、物流信息和用户信息等。本文将深入探讨1688 APP原数据API接口的开发、应用及其带来的商业收益,包括提升流量、优化库存管理、增强用户体验等方面。
66 6