Android中基于PHP+MySQL实现ListView点击跳转显示关联产品列表咨询
嘿,这个需求我熟!咱们一步步来实现,从数据库到PHP接口再到Android端,全流程给你捋清楚~
实现步骤详解
1. 数据库层面准备
首先得确保你的数据库里有两张关联表,对应你的两级列表需求:
- 分类表(比如
categories):存储第一个ListView的项,字段包括id(主键)、name(分类名称)、image_url(分类图片地址) - 产品表(比如
products):存储关联产品,字段包括id(主键)、category_id(关联分类表的id)、product_name(产品名)、product_image(产品图)、price(价格)
这样点击某个分类时,就能通过category_id筛选出对应的所有产品。
2. 编写PHP接口
需要两个接口:一个获取分类列表,另一个根据分类ID拉取关联产品。
接口1:获取分类列表(get_categories.php)
<?php // 替换成你的数据库连接信息 $conn = mysqli_connect("localhost", "db_username", "db_password", "db_name"); if (!$conn) { die("连接失败: " . mysqli_connect_error()); } $sql = "SELECT * FROM categories"; $result = mysqli_query($conn, $sql); $categories = []; if (mysqli_num_rows($result) > 0) { while($row = mysqli_fetch_assoc($result)) { $categories[] = $row; } } // 返回JSON格式数据,方便Android解析 echo json_encode($categories); mysqli_close($conn); ?>
接口2:根据分类ID获取产品(get_products.php)
这个接口需要接收前端传的category_id参数,一定要用预处理语句防止SQL注入:
<?php $conn = mysqli_connect("localhost", "db_username", "db_password", "db_name"); if (!$conn) { die("连接失败: " . mysqli_connect_error()); } // 获取前端传递的分类ID $category_id = $_GET['category_id']; // 预处理SQL语句 $sql = "SELECT * FROM products WHERE category_id = ?"; $stmt = mysqli_prepare($conn, $sql); mysqli_stmt_bind_param($stmt, "i", $category_id); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); $products = []; if (mysqli_num_rows($result) > 0) { while($row = mysqli_fetch_assoc($result)) { $products[] = $row; } } echo json_encode($products); mysqli_close($conn); ?>
3. Android端实现
第一步:配置权限和依赖
在AndroidManifest.xml中添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
如果用Retrofit做网络请求(比原生HttpURLConnection更方便),在build.gradle(Module level)中添加依赖:
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.github.bumptech.glide:glide:4.12.0' // 用于加载网络图片
第二步:创建数据模型类
对应数据库的表结构,创建两个实体类:
Category.java(分类数据):
public class Category { private int id; private String name; private String image_url; // Getter和Setter方法 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getImage_url() { return image_url; } public void setImage_url(String image_url) { this.image_url = image_url; } }
Product.java(产品数据):
public class Product { private int id; private String product_name; private String product_image; private double price; // Getter和Setter方法 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getProduct_name() { return product_name; } public void setProduct_name(String product_name) { this.product_name = product_name; } public String getProduct_image() { return product_image; } public void setProduct_image(String product_image) { this.product_image = product_image; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } }
第三步:创建Retrofit接口
定义网络请求的接口方法:
public interface ApiService { @GET("get_categories.php") Call<List<Category>> getCategories(); @GET("get_products.php") Call<List<Product>> getProducts(@Query("category_id") int categoryId); }
第四步:实现第一个ListView(分类列表)
在MainActivity中加载分类数据,并处理点击跳转逻辑:
public class MainActivity extends AppCompatActivity { private ListView categoryListView; private List<Category> categoryList; private CategoryAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); categoryListView = findViewById(R.id.category_list); categoryList = new ArrayList<>(); adapter = new CategoryAdapter(this, categoryList); categoryListView.setAdapter(adapter); // 初始化Retrofit,替换成你的服务器接口根地址 Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://你的服务器IP/项目目录/") .addConverterFactory(GsonConverterFactory.create()) .build(); ApiService apiService = retrofit.create(ApiService.class); // 请求分类数据 Call<List<Category>> call = apiService.getCategories(); call.enqueue(new Callback<List<Category>>() { @Override public void onResponse(Call<List<Category>> call, Response<List<Category>> response) { if (response.isSuccessful() && response.body() != null) { categoryList.addAll(response.body()); adapter.notifyDataSetChanged(); } } @Override public void onFailure(Call<List<Category>> call, Throwable t) { Toast.makeText(MainActivity.this, "分类加载失败: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } }); // 点击分类项跳转到产品列表页 categoryListView.setOnItemClickListener((parent, view, position, id) -> { Category selectedCategory = categoryList.get(position); Intent intent = new Intent(MainActivity.this, ProductsActivity.class); intent.putExtra("CATEGORY_ID", selectedCategory.getId()); startActivity(intent); }); } }
然后编写分类列表的适配器CategoryAdapter.java:
public class CategoryAdapter extends ArrayAdapter<Category> { private Context context; private List<Category> list; public CategoryAdapter(Context context, List<Category> list) { super(context, 0, list); this.context = context; this.list = list; } @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { View listItem = convertView == null ? LayoutInflater.from(context).inflate(R.layout.category_list_item, parent, false) : convertView; Category currentCategory = list.get(position); TextView nameTV = listItem.findViewById(R.id.category_name); ImageView imageIV = listItem.findViewById(R.id.category_image); nameTV.setText(currentCategory.getName()); // 用Glide加载网络图片 Glide.with(context).load(currentCategory.getImage_url()).into(imageIV); return listItem; } }
对应的category_list_item.xml布局(可根据需求调整样式):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp" android:orientation="horizontal"> <ImageView android:id="@+id/category_image" android:layout_width="80dp" android:layout_height="80dp" android:scaleType="centerCrop"/> <TextView android:id="@+id/category_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="16dp" android:textSize="18sp"/> </LinearLayout>
第五步:实现产品列表页面(ProductsActivity)
接收传递的分类ID,请求并展示关联产品:
public class ProductsActivity extends AppCompatActivity { private ListView productListView; private List<Product> productList; private ProductAdapter adapter; private int categoryId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_products); productListView = findViewById(R.id.product_list); productList = new ArrayList<>(); adapter = new ProductAdapter(this, productList); productListView.setAdapter(adapter); // 获取传递的分类ID categoryId = getIntent().getIntExtra("CATEGORY_ID", -1); if (categoryId == -1) { Toast.makeText(this, "无效的分类ID", Toast.LENGTH_SHORT).show(); finish(); return; } // 请求产品数据 Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://你的服务器IP/项目目录/") .addConverterFactory(GsonConverterFactory.create()) .build(); ApiService apiService = retrofit.create(ApiService.class); Call<List<Product>> call = apiService.getProducts(categoryId); call.enqueue(new Callback<List<Product>>() { @Override public void onResponse(Call<List<Product>> call, Response<List<Product>> response) { if (response.isSuccessful() && response.body() != null) { productList.addAll(response.body()); adapter.notifyDataSetChanged(); } } @Override public void onFailure(Call<List<Product>> call, Throwable t) { Toast.makeText(ProductsActivity.this, "产品加载失败: " + t.getMessage(), Toast.LENGTH_SHORT).show(); } }); } }
产品适配器ProductAdapter.java和对应的product_list_item.xml布局逻辑和分类适配器一致,只需替换为产品的字段即可,这里就不重复赘述了。
关键注意事项
- 网络请求线程:上面用了Retrofit的异步请求,避免了主线程阻塞;如果用原生请求,一定要手动开启子线程。
- 服务器地址:Android模拟器中不能用
localhost,要替换成10.0.2.2;真机测试需确保手机和服务器在同一局域网,使用服务器的局域网IP。 - 图片加载:用Glide/Picasso这类库,自带缓存和线程处理,比自己写Bitmap加载更稳定。
- 安全问题:PHP接口必须用预处理语句防止SQL注入,不要直接拼接SQL字符串。
内容的提问来源于stack exchange,提问作者akash
相关产品推荐
相关产品推荐

