安卓小游戏 2048 新手练手项目 完整代码(含注释)

本文介绍了作者根据极客教程学习并自制安卓版2048游戏的过程。游戏实现了创建卡片、合并动画功能,但未添加移动动画。详细分享了项目结构、XML布局文件、Java代码以及游戏逻辑,包括分数管理、触摸事件处理和滑动操作。适合初学者练习Android应用开发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看了极客的安卓2048的开发教程,大概了解了一下思路,然后自己就开始写了。后来发现这个设计思路不是太好,不方便加移动动画,就只加了创建卡片和合并的动画,不过用来练手还可以。

游戏截图如下:
在这里插入图片描述
如果想拷贝到本地运行的话,注意修改和包名相关的地方
或者在创建工程的时候按照以下命名:

项目名: Game2048
包名: pers.hurric.game2048

AndroidManifest.xml

仅需设置screenOrientation

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="pers.hurric.game2048">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">
    
    <TextView
        android:id="@+id/textScore"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/score"
        android:textColor="@color/colorPrimary"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toStartOf="@+id/guideline2"
        app:layout_constraintStart_toStartOf="@+id/guideline6"
        app:layout_constraintTop_toTopOf="@+id/guideline3" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

    <Button
        android:id="@+id/buttonReplay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/button_replay"
        android:textColor="@android:color/holo_blue_light"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline3" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.12" />

    <TextView
        android:id="@+id/textHighestScore"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/highest_score"
        android:textColor="@android:color/holo_purple"
        android:textSize="18sp"
        android:textStyle="italic"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="parent" />

    <pers.hurric.game2048.GameView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintStart_toStartOf="@+id/guideline6"
        app:layout_constraintTop_toTopOf="@+id/guideline4" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.26" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.9" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.05109489" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.95" />
</androidx.constraintlayout.widget.ConstraintLayout>

string.xml

<resources>
    <string name="app_name">Game2048</string>
    <string name="score">Score : 0</string>
    <string name="highest_score">Highest Score : 0</string>
    <string name="button_replay">Restart</string>
</resources>

color.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
    <color name="gameViewBackgroundColor">#D2B48C</color>
    <color name="backgroundColor0">#DEB887</color>
    <color name="textColor0">#00000000</color>
    <color name="textColor2">#333300</color>
    <color name="textColor4">#333300</color>
    <color name="textColorCommon">#FFFAFA</color>
    <color name="backgroundColor2">#FDF5E6</color>
    <color name="backgroundColor4">#FFE4B5</color>
    <color name="backgroundColor8">#FFE4C4</color>
    <color name="backgroundColor16">#FFDAB9</color>
    <color name="backgroundColor32">#FF7F50</color>
    <color name="backgroundColor64">#FF4500</color>
    <color name="backgroundColor128">#FF8C00</color>
    <color name="backgroundColor256">#FFD700</color>
    <color name="backgroundColor512">#9ACD32</color>
    <color name="backgroundColor1024">#483D8B</color>
    <color name="backgroundColor2048">#4B0082</color>
    <color name="backgroundColorBiggerThan2048">#000000</color>
</resources>

MainActivity.java

package pers.hurric.game2048;

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private final String FILE_NAME = "MyData";
    private final String HIGHEST_SCORE = "highest_score";

    private TextView textScore;
    private TextView textHighestScore;
    private Button buttonReplay;

    private int score = 0;
    private int highestScore = 0;

    public static MainActivity mainActivity;

    public MainActivity(){
        mainActivity = this;
    }

    public void addScore(int score) {
        this.score += score;
        textScore.setText("Score : " + this.score);
        //更新最高分
        updateHighestScore(this.score);
    }

    private void updateHighestScore(int score){
        if(score > highestScore){
            highestScore = score;
            textHighestScore.setText("HighestScore : " + score);
			//存储最高分
            SharedPreferences shp = getSharedPreferences(FILE_NAME, MODE_PRIVATE);
            SharedPreferences.Editor editor = shp.edit();

            editor.putInt(HIGHEST_SCORE, highestScore);
            editor.apply();
        }
    }

    public void clearScore(){
        score = 0;
        textScore.setText("Score : " + 0);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textScore = findViewById(R.id.textScore);
        textHighestScore = findViewById(R.id.textHighestScore);
        buttonReplay = findViewById(R.id.buttonReplay);

		//读取最高分
        SharedPreferences shp = getSharedPreferences(FILE_NAME, MODE_PRIVATE);
        highestScore = shp.getInt(HIGHEST_SCORE, 0);
        textHighestScore.setText("HighestScore : " + highestScore);

        buttonReplay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                GameView.gameView.replayGame();
            }
        });
    }

    private boolean isExit = false;

    class ExitHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 0){
                isExit = false;//修改状态为退出
            }
        }

    };

    ExitHandler mHandler = new ExitHandler();

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if(keyCode == KeyEvent.KEYCODE_BACK){
            if(!isExit){
                isExit = true;
                Toast.makeText(this, "再按一次退出游戏", Toast.LENGTH_SHORT).show();
                //延迟更改状态信息
                mHandler.sendEmptyMessageDelayed(0, 2000);
            }
            else{
                finish();
            }
        }
        return false;
    }
}

GameView.java

自定义游戏面板

package pers.hurric.game2048;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.widget.GridLayout;
import android.widget.Toast;

import java.util.Random;

public class GameView extends GridLayout {

    public static GameView gameView;

    private Card[][] cards = new Card[4][4];

    public GameView(Context context) {
        super(context);
        gameView = this;
        initGame();
    }

    public GameView(Context context, AttributeSet attrs) {
        super(context, attrs);
        gameView = this;
        initGame();
    }

    public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        gameView = this;
        initGame();
    }

    public void initGame(){
        this.setBackgroundColor(getResources().getColor(R.color.gameViewBackgroundColor));
        setColumnCount(4);

        int cardWidth = GetCardWidth();
        addCards(cardWidth, cardWidth);

        randomCreateCard(2);

        setListener();
    }

    public void replayGame(){
        MainActivity.mainActivity.clearScore();
        for(int i = 0; i < 4; ++i){
            for(int j = 0; j < 4; ++j){
                cards[i][j].setNum(0);
            }
        }
        randomCreateCard(2);
    }

	/*
	 * 监听Touch事件
	 */
    private void setListener(){
        setOnTouchListener(new OnTouchListener() {
            private float staX,  staY, endX, endY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch(event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        staX = event.getX();
                        staY = event.getY();
                        break;

                    case MotionEvent.ACTION_UP:
                        endX = event.getX();
                        endY = event.getY();

                        boolean swiped = false;//记录是否有效滑动了

                        //水平移动更多
                        if(Math.abs(endX - staX) > Math.abs(endY - staY)){
                            if(endX - staX > 10){
                                if(swipeRight()){
                                    swiped = true;
                                }
                            }
                            else if(endX - staX < -10){
                                if(swipeLeft()){
                                    swiped = true;
                                }
                            }
                        }
                        else{
                            if(endY - staY < -10){
                                if(swipeUp()){
                                    swiped = true;
                                }
                            }
                            else if(endY - staY > 10){
                                if(swipeDown()){
                                    swiped = true;
                                }
                            }
                        }
                        //滑动后创建新块,并检查当前状态是否能滑动
                        if(swiped){
                            randomCreateCard(1);
                            if(!canSwipe()){
                                gameOver();
                            }
                        }
                        break;
                }
                return true;
            }
        });
    }

	/*
	 * 返回该次滑动是否有效(有卡片移动或合并)
	 */
    private boolean swipeUp(){
        boolean flag = false;
        for(int j = 0; j < 4; ++j){
            int ind = 0;
            //从上往下依次处理
            for(int i = 1; i < 4; ++i){
            	//如果是存在数字的,往上遍历
                if(cards[i][j].getNum() != 0){
                    for(int ii = i - 1; ii >= ind; --ii){
                    	//如果这块是空的,将数字上移
                        if(cards[ii][j].getNum() == 0){
                            cards[ii][j].setNum(cards[i][j].getNum());
                            cards[i][j].setNum(0);
                            i--;//上移
                            flag = true;
                        }
                        //如果这块是相同的数,合并,合并的块不能一下合并两次,更新ind,不再遍历合并的块
                        else if(cards[ii][j].getNum() == cards[i][j].getNum()){
                            cards[ii][j].setNum((cards[i][j].getNum() * 2));
                            cards[i][j].setNum(0);
                            flag = true;
                            ind = ii + 1;//已经合过,该点不再合成
                            MainActivity.mainActivity.addScore(cards[ii][j].getNum() / 2);
                            //播放合并动画
                            playMergeAnimation(ii, j);
                            break;
                        }
                        //上面的块数字不同,退出循环
                        else break;
                    }
                }
            }
        }
        return flag;
    }

    private boolean swipeDown(){
        boolean flag = false;
        for(int j = 0; j < 4; ++j){
            int ind = 4;
            for(int i = 2; i >= 0; --i){
                if(cards[i][j].getNum() != 0){
                    for(int ii = i + 1; ii < ind; ++ii){
                        if(cards[ii][j].getNum() == 0){
                            cards[ii][j].setNum(cards[i][j].getNum());
                            cards[i][j].setNum(0);
                            flag = true;
                            i++;
                        }
                        else if(cards[ii][j].getNum() == cards[i][j].getNum()){
                            cards[ii][j].setNum((cards[i][j].getNum() * 2));
                            cards[i][j].setNum(0);
                            flag = true;
                            ind = ii;
                            MainActivity.mainActivity.addScore(cards[ii][j].getNum() / 2);
                            playMergeAnimation(ii, j);
                            break;
                        }
                        else break;
                    }
                }
            }
        }
        return flag;
    }

    private boolean swipeLeft(){
        boolean flag = false;
        for(int i = 0; i < 4; ++i){
            int ind = 0;
            for(int j = 1; j < 4; ++j){
                if(cards[i][j].getNum() != 0){
                    for(int jj = j - 1; jj >= ind; --jj){
                        if(cards[i][jj].getNum() == 0){
                            cards[i][jj].setNum(cards[i][j].getNum());
                            cards[i][j].setNum(0);
                            flag = true;
                            j--;
                        }
                        else if(cards[i][jj].getNum() == cards[i][j].getNum()){
                            cards[i][jj].setNum((cards[i][j].getNum() * 2));
                            cards[i][j].setNum(0);
                            flag = true;
                            ind = jj + 1;
                            MainActivity.mainActivity.addScore(cards[i][jj].getNum() / 2);
                            playMergeAnimation(i, jj);
                            break;
                        }
                        else break;
                    }
                }
            }
        }
        return flag;
    }

    private boolean swipeRight(){
        boolean flag = false;
        for(int i = 0; i < 4; ++i){
            int ind = 4;
            for(int j = 2; j >= 0; --j){
                if(cards[i][j].getNum() != 0){
                    for(int jj = j + 1; jj < ind; ++jj){
                        if(cards[i][jj].getNum() == 0){
                            cards[i][jj].setNum(cards[i][j].getNum());
                            cards[i][j].setNum(0);
                            flag = true;
                            j++;
                        }
                        else if(cards[i][jj].getNum() == cards[i][j].getNum()){
                            cards[i][jj].setNum((cards[i][j].getNum() * 2));
                            cards[i][j].setNum(0);
                            flag = true;
                            ind = jj;
                            MainActivity.mainActivity.addScore(cards[i][jj].getNum() / 2);
                            playMergeAnimation(i, jj);
                            break;
                        }
                        else break;
                    }
                }
            }
        }
        return flag;
    }

	/**
	  *如果存在空白块,或者相邻的数字相同的块,则可以继续滑动
	  */
    private boolean canSwipe(){
        for(int i = 0; i < 4; ++i){
            for(int j = 0; j < 4; ++j){
                if(cards[i][j].getNum() == 0){
                    return true;
                }
                else if(i != 3 && cards[i][j].getNum() == cards[i + 1][j].getNum()){
                    return true;
                }
                else if(j != 3 && cards[i][j].getNum() == cards[i][j + 1].getNum()){
                    return true;
                }
            }
        }
        return false;
    }

    private void addCards(int width, int height){
        Card c;
        for(int i = 0; i < 4; ++i){
            for(int j = 0; j < 4; ++j){
                c = new Card(getContext());
                addView(c, width, height);
                cards[i][j] = c;
            }
        }
    }

    private void gameOver(){
        Toast.makeText(getContext(), "游戏结束", Toast.LENGTH_SHORT).show();
    }

    private int GetCardWidth() {
    	//获取屏幕信息
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        //根据布局,GameView是占屏幕宽度的90%,除以4就是卡片边长
        return (int)((displayMetrics.widthPixels * 0.9f) / 4);
    }

	/*
	 * 递归随机,玄学复杂度,期望递归次数小于 16 次,偷了个懒
	 * 最好是把可用方块加入到一个列表中,然后在列表中随机
	 */
    private void randomCreateCard(int cnt){
        Random random = new Random();
        int r = random.nextInt(4);
        int c = random.nextInt(4);

		//该处已经存在数字,重新随机r, c
        if(cards[r][c].getNum() != 0){
            randomCreateCard(cnt);
            return;
        }

        int rand = random.nextInt(10);

        if(rand >= 2) rand = 2;
        else rand = 4;

        cards[r][c].setNum(rand);

		//播放创建动画
        playCreateAnimation(r, c);
		
        if(cnt >= 2){
            randomCreateCard(cnt - 1);
        }
    }

	/*
	 * 播放创建新块动画
	 */
    private void playCreateAnimation(int r, int c){
        AnimationSet animationSet = new AnimationSet(true);
		
		//旋转
        RotateAnimation anim = new RotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5f, RotateAnimation.RELATIVE_TO_SELF,0.5f);
        anim.setDuration(250);
        anim.setRepeatCount(0);
        anim.setInterpolator(new LinearInterpolator());

		//缩放
        ScaleAnimation anim2 = new ScaleAnimation(0,1,0,1,
                Animation.RELATIVE_TO_SELF,0.5f,
                Animation.RELATIVE_TO_SELF,0.5f
        );
        anim2.setDuration(250);
        anim2.setRepeatCount(0);

        animationSet.addAnimation(anim);
        animationSet.addAnimation(anim2);

        cards[r][c].startAnimation(animationSet);
    }

	/*
	 * 播放合并动画
	 */
    private void playMergeAnimation(int r, int c){
        ScaleAnimation anim = new ScaleAnimation(1,1.2f,1,1.2f,
                Animation.RELATIVE_TO_SELF,0.5f,
                Animation.RELATIVE_TO_SELF,0.5f
        );
        anim.setDuration(150);
        anim.setRepeatCount(0);

        anim.setRepeatMode(Animation.REVERSE);

        cards[r][c].startAnimation(anim);
    }
}

Card.java

代表一个格子

package pers.hurric.game2048;

import android.content.Context;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;

import java.util.HashMap;
import java.util.Map;

public class Card extends FrameLayout {

    TextView textView;

    private int num;

    static Map<Integer, Integer> backgroundColorIdMap = new HashMap<>();
    static Map<Integer, Integer> textColorIdMap = new HashMap<>();

    static {
        textColorIdMap.put(0, R.color.textColor0);
        textColorIdMap.put(2, R.color.textColor2);
        textColorIdMap.put(4, R.color.textColor4);

        backgroundColorIdMap.put(0, R.color.backgroundColor0);
        backgroundColorIdMap.put(2, R.color.backgroundColor2);
        backgroundColorIdMap.put(4, R.color.backgroundColor4);
        backgroundColorIdMap.put(8, R.color.backgroundColor8);
        backgroundColorIdMap.put(16, R.color.backgroundColor16);
        backgroundColorIdMap.put(32, R.color.backgroundColor32);
        backgroundColorIdMap.put(64, R.color.backgroundColor64);
        backgroundColorIdMap.put(128, R.color.backgroundColor128);
        backgroundColorIdMap.put(256, R.color.backgroundColor256);
        backgroundColorIdMap.put(512, R.color.backgroundColor512);
        backgroundColorIdMap.put(1024, R.color.backgroundColor1024);
    }

    public Card(@NonNull Context context) {
        super(context);

        textView = new TextView(context);
        textView.setGravity(Gravity.CENTER);
        textView.setText(getNum() + "");
        textView.setTextSize(50);

        setNum(0);

        LayoutParams lp = new LayoutParams(-1, -1);
        lp.setMargins(10, 10, 10, 10);

        addView(textView, lp);
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
        textView.setText(num + "");
        //数字改变时,同时改变改变字体大小和颜色
        changeColor(num);
        changeSize(num);
    }

    private void changeSize(int num){
        if(num >= 1024){
            textView.setTextSize(25);
        }
        else if(num >= 128){
            textView.setTextSize(35);
        }
        else if(num >= 16){
            textView.setTextSize(42);
        }
        else{
            textView.setTextSize(50);
        }
    }

    private void changeColor(int num){
        if(num >= 8){
            textView.setTextColor(getResources().getColor(R.color.textColorCommon));
        }
        else{
            textView.setTextColor(getResources().getColor(textColorIdMap.get(num)));
        }
        if(num >= 2048){
            textView.setBackgroundColor(getResources().getColor(R.color.backgroundColorBiggerThan2048));
        }
        else{
            textView.setBackgroundColor(getResources().getColor(backgroundColorIdMap.get(num)));
        }
    }
}

<think>好的,用户问的是“灵动岛是什么”,我需要先弄清楚这个术语的具体含义。首先,灵动岛听起来像是一个科技产品或功能的名字,可能跟智能手机有关。我记得苹果在iPhone 14 Pro系列中推出了一个叫做Dynamic Island的功能,中文翻译可能就是“灵动岛”。我需要确认这一。 接下来,我应该解释灵动岛的基本概念。它位于屏幕顶部的药丸形区域,原本是为了容纳前置摄像头和Face ID传感器而设计的。苹果通过软件将其转化为一个交互式的区域,显示实时活动和通知。这和之前的刘海屏设计不同,更具互动性。 然后需要详细说明它的主要功能,比如显示系统提醒(来电、Face ID验证、充电状态等),实时活动(音乐播放、计时器、导航等),以及交互操作(长按、轻触、滑动等手势)。这些功能让用户无需离开当前应用就能快速访问信息,提升使用效率。 还要提到它的设计理念,即通过软硬件结合,将原本的硬件缺陷转化为实用功能,体现了苹果的创新思维。同时,灵动岛的UI设计会根据内容变化形状和大小,保持视觉上的统一。 最后,可能补充一下适用机型,主要是iPhone 14 Pro和Pro Max,以及后续的iPhone 15系列是否支持。另外,可以提及用户和市场的反响,比如是否受到好评,有没有什么缺,比如适配问题或屏幕空间占用等。 检查是否有遗漏的信息,比如技术细节或与其他品牌的类似功能对比,但用户可能只需要基础介绍,所以保持简洁。确保使用中文,结构清晰,分说明,同时遵循系统级指令中的格式要求,数学公式部分可能用不上,但要注意其他格式规则。</think>### 灵动岛是什么? **灵动岛**(Dynamic Island)是苹果公司为iPhone 14 Pro及后续高端机型设计的一项交互式界面功能,旨在将屏幕顶部的“药丸形”挖孔区域(用于前置摄像头和Face ID传感器)转化为一个动态显示与交互的核心区域。它通过软硬件结合的方式,将原本静态的硬件缺陷转化为动态的信息中心。 --- ### 核心功能与特 1. **实时活动显示** 灵动岛可以动态展示当前后台任务的实时状态,例如: - 音乐播放(显示专辑封面、播放进度) - 通话计时或录音状态 - 导航指示、外卖配送进度 - 充电状态或设备连接提示 $$ \text{例:显示“正在播放:歌曲名 - 艺术家”} $$ 2. **交互式操作** 用户可以通过击、长按或滑动直接与灵动岛互动,无需跳转到对应App: - 轻触展开详细信息(如查看完整歌词) - 长按快速控制功能(如暂停音乐) - 滑动关闭任务 3. **系统通知整合** 将传统弹窗通知转化为更柔和的“气泡”样式,从灵动岛展开,减少对屏幕内容的遮挡。 4. **自适应UI设计** 灵动岛的形态会随内容动态变化,例如: - 来电时扩展为长条形显示通话人信息 - Face ID验证时短暂缩小为圆 --- ### 技术实现原理 - **软硬件协同**:通过OLED屏幕的局部亮度和色彩控制,隐藏挖孔区域的硬件边界。 - **动画过渡**:流畅的形变动画让视觉焦集中在信息本身,而非硬件形态。 --- ### 适配机型 目前支持灵动岛的机型包括: - iPhone 14 Pro / Pro Max - iPhone 15全系列 --- ### 评价与意义 - **创新性**:将硬件限制转化为功能亮,体现了“缺陷设计”的巧思。 - **争议**:部分用户认为其占用屏幕空间,且第三方App适配需时间完善。 - **行业影响**:推动了安卓厂商对类似交互设计的探索(如“灵动胶囊”)。 如果需要进一步了解具体操作或场景示例,可以随时提问!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值