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>

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

相关文章
|
4天前
|
Android开发 移动开发 小程序
binder机制原理面试,安卓app开发教程
binder机制原理面试,安卓app开发教程
binder机制原理面试,安卓app开发教程
|
3天前
|
前端开发 Android开发
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
|
3天前
|
Android开发
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
|
5天前
|
Java 开发工具 Android开发
Android mk 集成app
Android mk 集成app
14 5
|
5天前
|
移动开发 jenkins 持续交付
jenkins编译H5做的android端编译卫士app记录
jenkins编译H5做的android端编译卫士app记录
|
5天前
|
移动开发 小程序
如何让uni-app开发的H5页面顶部原生标题和小程序的顶部标题不一致?
如何让uni-app开发的H5页面顶部原生标题和小程序的顶部标题不一致?
|
5天前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
115 3
|
5天前
|
Android开发 开发者 UED
个人开发 App 成功上架手机应用市场的关键步骤
个人开发 App 成功上架手机应用市场的关键步骤
|
5天前
|
开发工具 数据安全/隐私保护 Android开发
【教程】APP 开发后如何上架?
【教程】APP 开发后如何上架?
|
5天前
|
API
uni-app 146朋友圈列表api开发
uni-app 146朋友圈列表api开发
21 0
http://www.vxiaotou.com