android:background和setImageResource的杂记

本文介绍了一种在Android应用中解决Out Of Memory (OOM)错误的方法,特别是在使用ViewPager展示大量图片时。通过优化图片加载方式,采用BitmapFactory.decodeStream替代setImageBitmap等方法,显著减少了内存占用。

公司开发了一个apk,在新手引导页面用viewpager放了四个界面,每个界面都是一个图片。

开发过程中一直没有问题,上线之后突然有一个三星的用户说一运行就崩溃。

后来调试一下,note3果然运行就崩溃。报OOM错。

viewpager的页面里面都是imageview实现的

ImageView view = new ImageView(NoiceGuideUI.this);
 view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
 view.setBackgroundResource(R.drawable.guide_2);
 view.setScaleType(ImageView.ScaleType.FIT_XY);

实在想不通,上网查了一下原来setBackgroundResource是有问题的。

搜索的内容:

====================================================================================================================================

尽量不要直接使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。 

因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。

如果在读取时加上图片的Config参数,可以跟有效减少加载的内存,从而跟有效阻止抛out of Memory异常

另外,decodeStream直接拿的图片来读取字节码了, 不会根据机器的各种分辨率来自动适应, 使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源, 否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。 


通过以下代码就可以避免OOm

/**
		 * 以最省内存的方式读取本地资源的图片
		 * 
		 * @param context
		 * @param resId
		 * @return
		 */
		private Bitmap readBitMap(Context context, int resId) {
			BitmapFactory.Options opt = new BitmapFactory.Options();
			opt.inPreferredConfig = Bitmap.Config.RGB_565;
			opt.inPurgeable = true;
			opt.inInputShareable = true;
			// 获取资源图片
			InputStream is = context.getResources().openRawResource(resId);
			return BitmapFactory.decodeStream(is, null, opt);
		}
然后将返回的bitmap设置给view============================================================================================================================


测试了一下果然是可以的。


package com.weishitechsub.kdcxqwb.fragment.Adapter; import android.Manifest; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.viewholder.BaseViewHolder; import com.weishitechsub.kdcxqwb.R; import com.weishitechsub.kdcxqwb.bean.ListBean; import com.weishitechsub.kdcxqwb.utils.PackageNotificationSender; import com.weishitechsub.kdcxqwb.utils.ReminderManager; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class MyCourierAdapter extends BaseQuickAdapter<ListBean, BaseViewHolder> { private static final String TAG = "MyCourierAdapter"; private List<ListBean> mTrackList; private Context context; // 存储:用户手动开启了提醒的单号 private final Set<String> remindedEnabled = ConcurrentHashMap.newKeySet(); // 存储:每个单号最后一次被提醒到的状态(用于去重) private final Map<String, String> remindedStates = new ConcurrentHashMap<>(); private Handler mainHandler = new Handler(Looper.getMainLooper()); int type; // 缓存当前列表数据,用于根据单号查找最新状态 private List<ListBean> currentList; public MyCourierAdapter(List<ListBean> list, Context context, int type) { super(R.layout.my_courier_adapter, list); this.context = context; this.type = type; ReminderManager.getInstance(context); // 初始化单例 } @Override protected void convert(@NonNull BaseViewHolder baseViewHolder, ListBean dataBean) { String number = dataBean.getNum(); baseViewHolder.setText(R.id.tv_number, number); baseViewHolder.setText(R.id.tv_address, dataBean.getContext()); if (type == 1) { baseViewHolder.getView(R.id.tv_remind).setVisibility(View.GONE); baseViewHolder.getView(R.id.iv_remind).setVisibility(View.GONE); } else { baseViewHolder.getView(R.id.tv_remind).setVisibility(View.VISIBLE); baseViewHolder.getView(R.id.iv_remind).setVisibility(View.VISIBLE); } // 设置快递公司信息图标 if (dataBean.getType() != null) { String trackName = getTrackName(dataBean.getType()); baseViewHolder.setText(R.id.tv_type, trackName); ImageView logo = baseViewHolder.getView(R.id.iv_logo); LinearLayout line = baseViewHolder.getView(R.id.line); setCompanyLogo(trackName, logo, line); } // ===== 获取状态文本 ===== String state = dataBean.getState(); String stateText = getStateText(state); baseViewHolder.setText(R.id.tv_state, stateText != null ? stateText : "未知状态"); ImageView ivRemind = baseViewHolder.getView(R.id.iv_remind); // === 统一处理所有状态分支 === if (state == null || TextUtils.isEmpty(stateText)) { // 情况1:状态无效 ivRemind.setEnabled(false); ivRemind.setAlpha(0.4f); refreshRemindIcon(ivRemind, false); ivRemind.setOnClickListener(v -> { Toast.makeText(context.getApplicationContext(), "该快递状态异常,暂不支持提醒功能", Toast.LENGTH_SHORT).show(); }); } else if (isTerminatedState(state)) { // 情况2:终止状态 → 禁止开启提醒,但允许点击查看提示 ivRemind.setEnabled(true); // ✅ 保持可点击 ivRemind.setAlpha(0.5f); // ✅ 视觉上变灰,表示不可用 refreshRemindIcon(ivRemind, false); // 显示关闭图标 ivRemind.setOnClickListener(v -> { String tip; if (state.startsWith("3")) { tip = getSignReceivedTip(state); } else if (state.startsWith("4")) { tip = "该快递已拒签,订单结束。"; } else if (state.startsWith("6")) { tip = "该快递已被退回。"; } else { tip = "该快递已完成,无需提醒。"; } Toast.makeText(context.getApplicationContext(), tip, Toast.LENGTH_SHORT).show(); }); // 自动取消本地提醒标记 ReminderManager.getInstance(context).disableReminder(number); remindedStates.put(number, state); } // 更新缓存列表 currentList = getData(); } // 检查权限后开启提醒,并发送通知 private void enableReminderWithPermissionCheck(ListBean dataBean) { String number = dataBean.getNum(); String state = dataBean.getState(); String type = dataBean.getType(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { showNotificationPermissionDialog(); return; } } ReminderManager.getInstance(context).enableReminder(number); Toast.makeText(context, "已开启提醒: " + number, Toast.LENGTH_SHORT).show(); String companyName = getTrackName(type); sendStatusNotification(number, companyName, getStateText(state), getNotifyContent(state)); } // 刷新提醒按钮图标 private void refreshRemindIcon(ImageView ivRemind, boolean isEnabled) { if (isEnabled) { ivRemind.setImageResource(R.mipmap.button_on); } else { ivRemind.setImageResource(R.mipmap.button_off); } } // 获取主状态文本(兼容 3xx) private String getStateText(String state) { if (state == null || state.isEmpty()) return null; String mainState = state.substring(0, 1); switch (mainState) { case "0": return "在途"; case "5": return "派件"; case "3": return "签收"; case "6": return "退回"; case "4": return "退签"; case "1": return "揽收"; case "7": return "转投"; case "2": return "疑难"; case "8": return getDetailClearanceText(state); default: return null; } } // 清关详情 private String getDetailClearanceText(String state) { switch (state) { case "8": return "清关"; case "10": return "待清关"; case "11": return "清关中"; case "12": return "已清关"; case "13": return "清关异常"; default: return "清关"; } } // 是否为终止状态(3xx, 4xx, 6xx) private boolean isTerminatedState(String state) { if (state == null || state.isEmpty()) return false; char firstChar = state.charAt(0); return firstChar == '3' || firstChar == '4' || firstChar == '6'; } // 签收子状态提示 private String getSignReceivedTip(String state) { switch (state) { case "301": return "该快递已由本人签收,无需提醒。"; case "302": return "该快递在派件异常后完成签收。"; case "303": return "该快递已被他人代签,请注意查收。"; case "304": return "该快递已投入快递柜或驿站签收。"; default: return "该快递已签收,无需提醒。"; } } // 获取通知内容 private String getNotifyContent(String state) { if (state.startsWith("3")) return getSignReceivedTip(state); switch (state) { case "0": return "您的快递已在运输途中,正在路上~"; case "5": return "快递员正在为您派送,请注意查收!"; case "6": return "很遗憾,您的快递因故被退回。"; case "4": return "您已拒签该快递,订单已完成。"; default: return "快递因地址问题已转投其他网点。"; } } // 发送状态变更通知 private void sendStatusNotification(String number, String company, String titleSuffix, String content) { PackageNotificationSender sender = new PackageNotificationSender(context); int id = ("status_" + number).hashCode() & Integer.MAX_VALUE; id = (id % 1_000_000) + 2000; sender.sendCustomNotification(id, "📌 快递" + titleSuffix, content, number, company); } // 根据 type 查找快递公司名称 private String getTrackName(String type) { if (mTrackList != null) { for (ListBean item : mTrackList) { if (item.getNum() != null && type != null && TextUtils.equals(item.getNum(), type)) { return item.getCom(); } } } return ""; } // 设置外部数据源 public void setTrackList(List<ListBean> list) { this.mTrackList = list; } // 缓存当前列表 @Override public void setNewInstance(List<ListBean> list) { super.setNewInstance(list); this.currentList = list; } // 获取所有开启提醒的条目 public List<ListBean> getRemindedItems() { List<ListBean> result = new ArrayList<>(); if (currentList == null) return result; Set<String> remindedNumbers = ReminderManager.getInstance(context).getRemindedNumbers(); for (ListBean bean : currentList) { if (remindedNumbers.contains(bean.getNum())) { result.add(bean); } } return result; } /** * 根据快递公司名称设置对应的 Logo 图标 */ private void setCompanyLogo(String name, ImageView logo, LinearLayout line) { if (name == null || name.isEmpty()) { logo.setVisibility(View.INVISIBLE); line.setVisibility(View.VISIBLE); return; } switch (name) { case "邮政快递包裹": logo.setImageResource(R.mipmap.img_yz_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "京东物流": logo.setImageResource(R.mipmap.img_jd_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "圆通快递": logo.setImageResource(R.mipmap.img_yt_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "中通快递": logo.setImageResource(R.mipmap.img_zt_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "顺丰速运": logo.setImageResource(R.mipmap.img_sf_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "韵达快递": logo.setImageResource(R.mipmap.img_yd_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "申通快递": logo.setImageResource(R.mipmap.img_st_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; case "EMS": logo.setImageResource(R.mipmap.img_ems_bg); logo.setVisibility(View.VISIBLE); line.setVisibility(View.GONE); break; default: logo.setVisibility(View.GONE); line.setVisibility(View.VISIBLE); break; } } private void showNotificationPermissionDialog() { if (context == null) return; new AlertDialog.Builder(context) .setTitle("需要通知权限") .setMessage("请允许本应用发送快递状态提醒通知。\n\n进入【设置】→【通知】中开启权限。") .setPositiveButton("去开启", (dialog, which) -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromParts("package", context.getPackageName(), null)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); }) .setNegativeButton("取消", (dialog, which) -> { Toast.makeText(context, "未授予通知权限,无法开启提醒", Toast.LENGTH_SHORT).show(); }) .create() .show(); } } <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@drawable/outcome_bg" android:paddingTop="16dp" android:paddingLeft="@dimen/anythink_14dp" android:paddingRight="@dimen/anythink_14dp" > <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:id="@+id/tv_number" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="12345678998765" android:textSize="18sp" /> <TextView android:id="@+id/tv_remind" android:layout_width="wrap_content" android:layout_height="25dp" android:text="取件提醒" android:textColor="@color/black" android:textSize="@dimen/sp_14" android:padding="3dp" android:layout_toRightOf="@id/tv_number" android:layout_marginLeft="@dimen/base_dp_8" /> <ImageView android:id="@+id/iv_remind" android:layout_width="40dp" android:layout_height="30dp" android:src="@mipmap/button_off" android:layout_toRightOf="@+id/tv_remind" android:layout_centerVertical="true" /> <TextView android:id="@+id/tv_state" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="派件中" android:textSize="14sp" android:textColor="#2281FF" android:layout_alignParentRight="true" /> </RelativeLayout> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:layout_marginBottom="@dimen/dp_10" > <ImageView android:id="@+id/iv_logo" android:layout_width="46dp" android:layout_height="@dimen/dp_40" android:src="@mipmap/img_jd_bg" android:visibility="invisible" /> <LinearLayout android:id="@+id/line" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="@dimen/dp_10" android:background="@drawable/round_corner_line" > <TextView android:id="@+id/tv_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="1111" android:textColor="@color/white" android:layout_gravity="center" /> </LinearLayout> <TextView android:id="@+id/tv_type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="京东快递" android:textSize="15sp" android:textColor="#333333" android:layout_toRightOf="@id/iv_logo" android:layout_marginLeft="@dimen/anythink_12dp" /> <TextView android:id="@+id/tv_address" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="【合肥市】合肥市庐阳区四里河街道在派件" android:textSize="12sp" android:textColor="#666666" android:layout_toRightOf="@id/iv_logo" android:layout_below="@id/tv_type" android:layout_marginLeft="@dimen/anythink_12dp" android:maxLines="2" android:ellipsize="end" /> </RelativeLayout> </LinearLayout>这段代码没有快递logo显示<LinearLayout android:id="@+id/line" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="@dimen/dp_10" android:background="@drawable/round_corner_line" > <TextView android:id="@+id/tv_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="1111" android:textColor="@color/white" android:layout_gravity="center" /> </LinearLayout>这个,隐藏iv_logo这个控件但是tv_type、tv_address,字体会盖到line这个控件上
最新发布
11-29
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="@drawable/animal_bg" android:padding="16dp"> <GridLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:columnCount="4" android:rowCount="2" android:useDefaultMargins="false" android:gap="8dp"> <!-- 第1行 --> <ImageView android:layout_width="80dp" android:layout_height="80dp" android:background="@drawable/five" android:clickable="true" android:focusable="true" /> <ImageView android:layout_width="80dp" android:layout_height="80dp" android:background="@drawable/one" android:clickable="true" android:focusable="true" /> <ImageView android:layout_width="80dp" android:layout_height="80dp" android:background="@drawable/two" android:clickable="true" android:focusable="true" /> <ImageView android:layout_width="80dp" android:layout_height="80dp" android:background="@drawable/two" android:clickable="true" android:focusable="true" /> <!-- 第2行 --> <ImageView android:layout_width="80dp" android:layout_height="80dp" android:background="@drawable/five" android:clickable="true" android:focusable="true" /> <ImageView android:layout_width="80dp" android:layout_height="80dp" android:background="@drawable/one" android:clickable="true" android:focusable="true" /> <ImageView android:layout_width="80dp" android:layout_height="80dp" android:background="@drawable/two" android:clickable="true" android:focusable="true" /> <ImageView android:layout_width="80dp" android:layout_height="80dp" android:background="@drawable/five" android:clickable="true" android:focusable="true" /> </GridLayout> </LinearLayout>
11-15
<think> 我们有一个Android布局文件,它使用GridLayout来放置8个ImageView,分成两行四列。每个ImageView都有相同的宽高(80dp),并且设置了可点击可获得焦点。 背景使用drawable资源,如@drawable/five, @drawable/one等。 整个布局的背景是@drawable/animal_bg,并且有16dp的内边距。 但是,这个布局中每个ImageView都是独立设置的,如果我们想要实现一个记忆匹配游戏(翻牌匹配),那么我们需要为这些ImageView添加点击事件,并且能够翻转图片。 问题:如何实现点击ImageView后翻转图片,并检查是否匹配? 解决方案: 1. 我们需要为每个ImageView设置一个点击事件监听器。 2. 在点击事件中,我们需要记录当前点击的图片,并翻转它(显示另一面的图片)。 3. 我们需要设计一个机制来检查连续两次点击的图片是否匹配(即背景资源是否相同)。 步骤: a. 初始化时,每个ImageView的正面可能是动物图案,背面是统一的图案(比如卡背)。但是在这个布局中,我们看到的是正面,所以我们需要在代码中设置背面,或者使用一个状态来记录。 b. 我们可以将每个ImageView的tag设置为一个自定义对象,或者使用两个数组:一个记录正面的图片资源,一个记录背面的图片资源(所有背面相同)。 c. 我们还需要记录当前是正面还是背面。可以使用一个布尔数组来记录每个位置的状态,或者使用ImageView的tag来记录当前状态。 具体实现思路: 1. 在Activity中,我们获取所有的ImageView,并初始化一个数组来记录每个ImageView对应的动物资源(正面)背面资源。 2. 为每个ImageView设置相同的背面图片(比如@drawable/card_back)。 3. 我们还需要一个数组来记录每个位置的图片是否已经被匹配(匹配后可以设置为不可点击,或者移除监听器)。 4. 当点击一个ImageView时,如果它还没有被匹配并且当前是背面(即没有翻转过来),我们就将它翻转到正面(显示动物图片),并记录这次点击。如果连续点击两个,就检查这两个的正面图片是否相同。 5. 如果相同,则将这两个ImageView设置为已匹配(比如设置为不可点击,并改变透明度等)。如果不相同,则在短暂延迟后翻转回去(显示背面)。 注意:为了避免连续点击同一个图片,我们需要在点击后立即将其设置为不可点击,直到翻转动画完成或者检查完毕。 代码示例(在Activity中): 假设我们有一个布局文件名为activity_memory_game.xml,其中GridLayout的id为grid_layout。 以下是一个简化的实现: 首先,在Activity的onCreate方法中: </think> # Android卡片匹配游戏实现 基于您提供的布局文件,我将实现一个完整的卡片匹配游戏应用。这个游戏将展示一组卡片(动物图片),玩家需要点击卡片来翻转并寻找匹配对。 ## 设计思路 - 使用GridLayout展示8张卡片(2行×4列) - 实现卡片翻转动画效果 - 添加匹配逻辑游戏状态管理 - 设计美观的UI界面 - 添加游戏计时得分功能 下面是完整的实现代码: ```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp" android:background="#9C27B0" android:theme="@style/Theme.AppCompat.Light.DarkActionBar"> <!-- 标题区域 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center" android:padding="10dp" android:background="#7B1FA2" android:elevation="4dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="动物卡片匹配游戏" android:textSize="24sp" android:textColor="#FFFFFF" android:textStyle="bold" android:padding="8dp"/> </LinearLayout> <!-- 游戏信息区域 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="16dp" android:layout_marginBottom="24dp" android:weightSum="3"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center"> <TextView android:id="@+id/tvTime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="时间: 00:00" android:textSize="18sp" android:textColor="#FFFFFF"/> </LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center"> <TextView android:id="@+id/tvPairs" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="匹配: 0/4" android:textSize="18sp" android:textColor="#FFFFFF"/> </LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center"> <TextView android:id="@+id/tvMoves" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="步数: 0" android:textSize="18sp" android:textColor="#FFFFFF"/> </LinearLayout> </LinearLayout> <!-- 游戏卡片区域 --> <GridLayout android:id="@+id/gridLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:columnCount="4" android:rowCount="2" android:useDefaultMargins="false" android:padding="8dp" android:background="#673AB7" android:elevation="8dp"> <!-- 卡片将通过代码动态添加 --> </GridLayout> <!-- 控制按钮区域 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="24dp" android:gravity="center" android:weightSum="2"> <Button android:id="@+id/btnReset" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="8dp" android:text="重新开始" android:textAllCaps="false" android:backgroundTint="#FFC107" android:textColor="#212121"/> <Button android:id="@+id/btnHint" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginStart="8dp" android:text="显示提示" android:textAllCaps="false" android:backgroundTint="#4CAF50" android:textColor="#FFFFFF"/> </LinearLayout> </LinearLayout> ``` ## Java代码实现 (MemoryGameActivity.java) ```java import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorSet; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.widget.Button; import android.widget.GridLayout; import android.widget.ImageView; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class MemoryGameActivity extends AppCompatActivity { private GridLayout gridLayout; private TextView tvTime, tvPairs, tvMoves; private Button btnReset, btnHint; private List<AnimalCard> animalCards = new ArrayList<>(); private List<Integer> cardIds = Arrays.asList( R.drawable.ic_lion, R.drawable.ic_lion, R.drawable.ic_tiger, R.drawable.ic_tiger, R.drawable.ic_panda, R.drawable.ic_panda, R.drawable.ic_elephant, R.drawable.ic_elephant ); private AnimalCard firstSelectedCard = null; private AnimalCard secondSelectedCard = null; private int matchedPairs = 0; private int totalMoves = 0; private long startTime = 0; private boolean isGameRunning = false; private Handler timerHandler = new Handler(); private Runnable timerRunnable = new Runnable() { @Override public void run() { if (isGameRunning) { long millis = System.currentTimeMillis() - startTime; int seconds = (int) (millis / 1000); int minutes = seconds / 60; seconds %= 60; tvTime.setText(String.format("时间: %02d:%02d", minutes, seconds)); timerHandler.postDelayed(this, 500); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_memory_game); // 初始化视图 gridLayout = findViewById(R.id.gridLayout); tvTime = findViewById(R.id.tvTime); tvPairs = findViewById(R.id.tvPairs); tvMoves = findViewById(R.id.tvMoves); btnReset = findViewById(R.id.btnReset); btnHint = findViewById(R.id.btnHint); // 设置点击监听器 btnReset.setOnClickListener(v -> resetGame()); btnHint.setOnClickListener(v -> showHint()); // 初始化游戏 resetGame(); } private void resetGame() { // 重置游戏状态 matchedPairs = 0; totalMoves = 0; firstSelectedCard = null; secondSelectedCard = null; isGameRunning = false; // 更新UI tvTime.setText("时间: 00:00"); tvPairs.setText("匹配: 0/4"); tvMoves.setText("步数: 0"); // 移除所有卡片 gridLayout.removeAllViews(); // 创建卡片 createCards(); // 重置后延迟显示卡片背面 new Handler().postDelayed(() -> { for (AnimalCard card : animalCards) { card.flipToBack(); } startGame(); }, 500); } private void createCards() { animalCards.clear(); Collections.shuffle(cardIds); for (int i = 0; i < cardIds.size(); i++) { int animalId = cardIds.get(i); // 创建卡片 AnimalCard card = new AnimalCard(this, animalId); card.setTag(i); // 设置唯一标识 // 设置点击监听器 card.setOnClickListener(v -> onCardClicked((AnimalCard) v)); // 添加到网格布局 GridLayout.LayoutParams params = new GridLayout.LayoutParams(); params.width = dpToPx(80); params.height = dpToPx(80); params.setMargins(dpToPx(8), dpToPx(8), dpToPx(8), dpToPx(8)); card.setLayoutParams(params); gridLayout.addView(card); animalCards.add(card); } } private void startGame() { startTime = System.currentTimeMillis(); isGameRunning = true; timerHandler.postDelayed(timerRunnable, 0); } private void onCardClicked(AnimalCard card) { // 如果卡片已经匹配或正在动画中,则忽略点击 if (card.isMatched() || card.isFlipping() || secondSelectedCard != null) { return; } // 翻转卡片 card.flipToFront(); if (firstSelectedCard == null) { // 第一次选择 firstSelectedCard = card; } else { // 第二次选择 secondSelectedCard = card; totalMoves++; tvMoves.setText("步数: " + totalMoves); // 检查是否匹配 if (firstSelectedCard.getAnimalId() == secondSelectedCard.getAnimalId()) { // 匹配成功 matchedPairs++; tvPairs.setText("匹配: " + matchedPairs + "/4"); // 设置为已匹配 firstSelectedCard.setMatched(true); secondSelectedCard.setMatched(true); // 重置选择 firstSelectedCard = null; secondSelectedCard = null; // 检查游戏是否结束 if (matchedPairs == 4) { endGame(); } } else { // 不匹配,翻转回来 new Handler().postDelayed(() -> { firstSelectedCard.flipToBack(); secondSelectedCard.flipToBack(); // 重置选择 firstSelectedCard = null; secondSelectedCard = null; }, 1000); } } } private void endGame() { isGameRunning = false; timerHandler.removeCallbacks(timerRunnable); // 显示胜利消息 new Handler().postDelayed(() -> { // 这里可以添加游戏结束对话框 }, 500); } private void showHint() { // 临时显示所有卡片正面 for (AnimalCard card : animalCards) { if (!card.isMatched() && card.isBackVisible()) { card.flipToFront(); new Handler().postDelayed(card::flipToBack, 1000); } } } private int dpToPx(int dp) { float density = getResources().getDisplayMetrics().density; return Math.round(dp * density); } // 自定义卡片类 private static class AnimalCard extends androidx.appcompat.widget.AppCompatImageView { private int animalId; private boolean isMatched = false; private boolean isFlipping = false; private boolean backVisible = true; public AnimalCard(Context context, int animalId) { super(context); this.animalId = animalId; setImageResource(R.drawable.card_back); setScaleType(ScaleType.CENTER_CROP); setBackgroundResource(R.drawable.card_border); } public int getAnimalId() { return animalId; } public boolean isMatched() { return isMatched; } public void setMatched(boolean matched) { isMatched = matched; } public boolean isFlipping() { return isFlipping; } public boolean isBackVisible() { return backVisible; } public void flipToFront() { if (!backVisible || isFlipping) return; flipCard(R.drawable.card_back, animalId); backVisible = false; } public void flipToBack() { if (backVisible || isFlipping) return; flipCard(animalId, R.drawable.card_back); backVisible = true; } private void flipCard(int fromResId, int toResId) { setImageResource(fromResId); AnimatorSet set = new AnimatorSet(); Animator flipOut = AnimatorInflater.loadAnimator(getContext(), R.animator.flip_out); Animator flipIn = AnimatorInflater.loadAnimator(getContext(), R.animator.flip_in); flipOut.setTarget(this); flipIn.setTarget(this); set.playSequentially(flipOut, flipIn); set.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { isFlipping = true; } @Override public void onAnimationEnd(Animator animation) { isFlipping = false; setImageResource(toResId); } @Override public void onAnimationCancel(Animator animation) {} @Override public void onAnimationRepeat(Animator animation) {} }); set.start(); } } } ``` ## 动画资源文件 ### res/animator/flip_in.xml ```xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:propertyName="rotationY" android:valueFrom="90" android:valueTo="0" android:duration="150"/> </set> ``` ### res/animator/flip_out.xml ```xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:propertyName="rotationY" android:valueFrom="0" android:valueTo="-90" android:duration="150"/> </set> ``` ## 图片资源 在res/drawable中添加以下图片资源: - card_back.png (卡片背面) - card_border.xml (卡片边框) - ic_lion.png (狮子) - ic_tiger.png (老虎) - ic_panda.png (熊猫) - ic_elephant.png (大象) ### card_border.xml ```xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="8dp" /> <stroke android:width="2dp" android:color="#FFFFFF" /> <solid android:color="#FFFFFF" /> </shape> ``` ## 代码解释 1. **游戏结构**: - 顶部标题栏显示游戏名称 - 游戏信息区显示时间、匹配对数步数 - 中间是卡片网格区域(2×4布局) - 底部是控制按钮(重新开始提示) 2. **核心功能**: - 卡片翻转动画:使用AnimatorSet实现3D翻转效果 - 匹配逻辑:记录并比较两次选择的卡片 - 游戏计时:使用Handler实现计时器 - 游戏状态管理:开始、结束重置 3. **自定义AnimalCard类**: - 继承ImageView并封装卡片状态(匹配状态、翻转状态) - 提供flipToFront()flipToBack()方法实现翻转动画 - 管理卡片正面背面图片资源 4. **游戏流程**: - 初始化时创建并随机排列卡片 - 玩家点击卡片触发翻转动画 - 两张卡片匹配则保持正面朝上 - 全部匹配完成后游戏结束 ## 相关优化建议 1. **添加音效**:在卡片翻转匹配成功时添加音效 2. **难度选择**:提供不同网格大小(如3×4、4×4)的难度选项 3. **游戏结束对话框**:显示最终得分用时 4. **本地存储**:保存最佳成绩 5. **卡片主题**:提供多种主题的卡片(动物、水果、标志等)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值