Android实现红包雨动画效果

本文介绍了Android实现红包雨动画效果,分享给大家,希望对大家有帮助

红包雨

关于实现上面红包雨效果步骤如下:

1.创建一个红包实体类

public class RedPacket {
  public float x, y;
  public float rotation;
  public float speed;
  public float rotationSpeed;
  public int width, height;
  public Bitmap bitmap;
  public int money;
  public boolean isRealRed;

  public RedPacket(Context context, Bitmap originalBitmap, int speed, float maxSize, float minSize, int viewWidth) {
    //获取一个显示红包大小的倍数
    double widthRandom = Math.random();
    if (widthRandom < minSize || widthRandom > maxSize) {
      widthRandom = maxSize;
    }
    //红包的宽度
    width = (int) (originalBitmap.getWidth() * widthRandom);
    //红包的高度
    height = width * originalBitmap.getHeight() / originalBitmap.getWidth();
    int mWidth = (viewWidth == 0) ? context.getResources().getDisplayMetrics().widthPixels : viewWidth;
    //生成红包bitmap
    bitmap = Bitmap.createScaledBitmap(originalBitmap, width, height, true);
    originalBitmap.recycle();
    Random random = new Random();
    //红包起始位置x:[0,mWidth-width]
    int rx = random.nextInt(mWidth) - width;
    x = rx <= 0 ? 0 : rx;
    //红包起始位置y
    y = -height;
    //初始化该红包的下落速度
    this.speed = speed + (float) Math.random() * 1000;
    //初始化该红包的初始旋转角度
    rotation = (float) Math.random() * 180 - 90;
    //初始化该红包的旋转速度
    rotationSpeed = (float) Math.random() * 90 - 45;
    //初始化是否为中奖红包
    isRealRed = isRealRedPacket();
  }

  /**
   * 判断当前点是否包含在区域内
   */
  public boolean isContains(float x, float y) {
    //稍微扩大下点击的区域
    return this.x-50 < x && this.x +50 + width > x
        && this.y-50 < y && this.y+50 + height > y;
  }

  /**
   * 随机 是否为中奖红包
   */
  public boolean isRealRedPacket() {
    Random random = new Random();
    int num = random.nextInt(10) + 1;
    //如果[1,10]随机出的数字是2的倍数 为中奖红包
    if (num % 2 == 0) {
      money = num*2;//中奖金额
      return true;
    }
    return false;
  }

  /**
   * 回收图片
   */
  public void recycle() {
    if (bitmap!= null && !bitmap.isRecycled()){
      bitmap.recycle();
    }
  }
}

上面就红包实体类的源码,重点就是在创建红包实体的时候,初始化红包相关的值,如生成红包图片,图片的宽高,红包初始位置,下落速度等。比较简单。

2.自定义红包雨view

view初始化

 public RedPacketTest(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RedPacketStyle);
    //获取xml中配置的view的style属性,如下落红包数量,下落的基础速度,以及红包图片的最大最小范围
    count = typedArray.getInt(R.styleable.RedPacketStyle_count, 20);
    speed = typedArray.getInt(R.styleable.RedPacketStyle_speed, 20);
    minSize = typedArray.getFloat(R.styleable.RedPacketStyle_min_size, 0.5f);
    maxSize = typedArray.getFloat(R.styleable.RedPacketStyle_max_size, 1.2f);
    typedArray.recycle();
    init();
  }

  /**
   * 初始化
   */
  private void init() {
    //初始化画笔
    paint = new Paint();
    paint.setFilterBitmap(true);
    paint.setDither(true);
    paint.setAntiAlias(true);
    //创建一个属性动画,通过属性动画来控制刷新红包下落的位置
    animator = ValueAnimator.ofFloat(0, 1);
    //绘制view开启硬件加速
    setLayerType(View.LAYER_TYPE_HARDWARE, null);
   //初始化属性动画
    initAnimator();
  }

  private void initAnimator() {
    //每次动画更新的时候,更新红包下落的坐标值
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        long nowTime = System.currentTimeMillis();
        //获取两次动画更新之间的时间,以此来计算下落的高度
        float secs = (float) (nowTime - prevTime) / 1000f;
        prevTime = nowTime;
        for (int i = 0; i < redpacketlist.size(); ++i) {
          RedPacket redPacket = redpacketlist.get(i);
          //更新红包的下落的位置y
          redPacket.y += (redPacket.speed * secs);

          //如果y坐标大于view的高度 说明划出屏幕,y重新设置起始位置,以及中奖属性
          if (redPacket.y > getHeight()) {
            redPacket.y = 0 - redPacket.height;
            redPacket.isRealRed = redPacket.isRealRedPacket();
          }
          //更新红包的旋转的角度
          redPacket.rotation = redPacket.rotation
              + (redPacket.rotationSpeed * secs);
        }
        //重绘
        invalidate();
      }
    });
    //属性动画无限循环
    animator.setRepeatCount(ValueAnimator.INFINITE);
    //属性值线性变换
    animator.setInterpolator(new LinearInterpolator());
    animator.setDuration(0);
  }

view绘制

 @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //获取自定义view的宽度
    mWidth = getMeasuredWidth();
  }

  @Override
  protected void onDraw(final Canvas canvas) {
    //遍历红包数组,绘制红包
    for (int i = 0; i < redpacketlist.size(); i++) {
      RedPacket redPacket = redpacketlist.get(i);
      //将红包旋转redPacket.rotation角度后 移动到(redPacket.x,redPacket.y)进行绘制红包
      Matrix m = new Matrix();
      m.setTranslate(-redPacket.width / 2, -redPacket.height / 2);
      m.postRotate(redPacket.rotation);
      m.postTranslate(redPacket.width / 2 + redPacket.x, redPacket.height / 2 + redPacket.y);
      //绘制红包
      canvas.drawBitmap(redPacket.bitmap, m, paint);
    }
  }

红包雨动画开始结束

 /**
   *停止动画
   */
  public void stopRainNow() {
    //清空红包数据
    clear();
    //重绘
    invalidate();
    //动画取消
    animator.cancel();
  }

  /**
   * 开始动画
   */
  public void startRain() {
    //清空红包数据
    clear();
    //添加红包
    setRedpacketCount(count);
    prevTime = System.currentTimeMillis();
    //动画开始
    animator.start();

  }

  public void setRedpacketCount(int count) {
    if (mImgIds == null || mImgIds.length == 0)
      return;
    for (int i = 0; i < count; ++i) {
      //获取红包原始图片
      Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), mImgIds[0]);
      //生成红包实体类
      RedPacket redPacket = new RedPacket(getContext(), originalBitmap, speed,maxSize,minSize,mWidth);
      //添加进入红包数组
      redpacketlist.add(redPacket);
    }
  }

  /**
   * 暂停红包雨
   */
  public void pauseRain() {
    animator.cancel();
  }

  /**
   * 重新开始
   */
  public void restartRain() {
    animator.start();
  }

  /**
   * 清空红包数据,并回收红包中的bitmap
   */
  private void clear() {
    for (RedPacket redPacket :redpacketlist) {
      redPacket.recycle();
    }
    redpacketlist.clear();
  }

红包点击事件

  @Override
  public boolean onTouchEvent(MotionEvent motionEvent) {
    switch (motionEvent.getAction()){
      case MotionEvent.ACTION_DOWN:
        //根据点击的坐标点,判断是否点击在红包的区域
        RedPacket redPacket = isRedPacketClick(motionEvent.getX(), motionEvent.getY());
        if (redPacket != null) {
          //如果点击在红包上,重新设置起始位置,以及中奖属性
          redPacket.y = 0 - redPacket.height;
          redPacket.isRealRed = redPacket.isRealRedPacket();
          if (onRedPacketClickListener != null) {
            onRedPacketClickListener.onRedPacketClickListener(redPacket);
          }
        }
        break;
      case MotionEvent.ACTION_MOVE:
        break;
      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:

        break;
    }
    return true;
  }

  //根据点击坐标点,遍历红包数组,看是否点击在红包上
  private RedPacket isRedPacketClick(float x, float y) {
    for (int i = redpacketlist.size() - 1; i >= 0; i --) {
      if (redpacketlist.get(i).isContains(x, y)) {
        return redpacketlist.get(i);
      }
    }
    return null;
  }

关于自定义红包雨view的主要代码以及分析基本完成了。下面是自定义view的使用。

3.自定义view的使用

红包雨Activity

public class RedPacketActivity extends AppCompatActivity implements View.OnClickListener {
  private RedPacketTest redRainView1;
  private Button start, stop;
  private TextView money;
  private int totalmoney = 0;
  AlertDialog.Builder ab;
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.red_rain);
    ab = new AlertDialog.Builder(RedPacketActivity.this);
    start = (Button) findViewById(R.id.start);
    stop = (Button) findViewById(R.id.stop);
    money = (TextView) findViewById(R.id.money);
    redRainView1 = (RedPacketTest) findViewById(R.id.red_packets_view1);
    start.setOnClickListener(this);
    stop.setOnClickListener(this);

  }

  @Override
  public void onClick(View v) {
    if (v.getId() == R.id.start) {
      startRedRain();
    } else if (v.getId() == R.id.stop) {
      stopRedRain();
    }
  }

  /**
   * 开始下红包雨
   */
  private void startRedRain() {
    redRainView1.startRain();
    redRainView1.setOnRedPacketClickListener(new RedPacketTest.OnRedPacketClickListener() {
      @Override
      public void onRedPacketClickListener(RedPacket redPacket) {
        redRainView1.pauseRain();
        ab.setCancelable(false);
        ab.setTitle("红包提醒");
        ab.setNegativeButton("继续抢红包", new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int which) {
            redRainView1.restartRain();
          }
        });

        if (redPacket.isRealRed) {
          ab.setMessage("恭喜你,抢到了" + redPacket.money + "元!");
          totalmoney += redPacket.money;
          money.setText("中奖金额: " + totalmoney);
        } else {
          ab.setMessage("很遗憾,下次继续努力!");
        }
        redRainView1.post(new Runnable() {
          @Override
          public void run() {
            ab.show();
          }
        });
      }
    });
  }

  /**
   * 停止下红包雨
   */
  private void stopRedRain() {
    totalmoney = 0;//金额清零
    redRainView1.stopRainNow();
  }

红包雨Activity的xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#80000000">

  <ImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="fitXY"
    android:src="@drawable/red_packets_bg" />
  <Button
    android:id="@+id/start"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="开始"
    />
  <Button
    android:id="@+id/stop"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:text="结束"
    />

  <TextView
    android:id="@+id/money"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:text="中奖金额:"
    android:textSize="18sp"
    android:layout_marginTop="10dp"
    />

  <com.example.test.redpacketrain.RedPacketTest
    android:id="@+id/red_packets_view1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:count="20"
    app:max_size="0.8"
    app:min_size="0.6"
    app:speed="500" />
</RelativeLayout>

自定义view的styleable

<resources>
  <declare-styleable name="RedPacketStyle">
    <attr name="count" format="integer" />
    <attr name="speed" format="integer" />
    <attr name="max_size" format="float" />
    <attr name="min_size" format="float" />
  </declare-styleable>
</resources>

完整的自定义view代码

public class RedPacketTest extends View {
  private int[] mImgIds = new int[]{
      R.drawable.red_packets_icon
  };//红包图片
  private int count;//红包数量
  private int speed;//下落速度
  private float maxSize;//红包大小的范围
  private float minSize;//红包大小的范围
  private int mWidth;//view宽度
  private ValueAnimator animator;//属性动画,用该动画来不断改变红包下落的坐标值

  private Paint paint;//画笔
  private long prevTime;
  private ArrayList<RedPacket> redpacketlist = new ArrayList<>();//红包数组

  public RedPacketTest(Context context) {
    super(context);
    init();
  }

  public RedPacketTest(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RedPacketStyle);
    count = typedArray.getInt(R.styleable.RedPacketStyle_count, 20);
    speed = typedArray.getInt(R.styleable.RedPacketStyle_speed, 20);
    minSize = typedArray.getFloat(R.styleable.RedPacketStyle_min_size, 0.5f);
    maxSize = typedArray.getFloat(R.styleable.RedPacketStyle_max_size, 1.2f);
    typedArray.recycle();
    init();
  }

  /**
   * 初始化
   */
  private void init() {
    paint = new Paint();
    paint.setFilterBitmap(true);
    paint.setDither(true);
    paint.setAntiAlias(true);
    animator = ValueAnimator.ofFloat(0, 1);
    setLayerType(View.LAYER_TYPE_HARDWARE, null);
    initAnimator();
  }

  private void initAnimator() {
    //每次动画更新的时候,更新红包下落的坐标值
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        long nowTime = System.currentTimeMillis();
        float secs = (float) (nowTime - prevTime) / 1000f;
        prevTime = nowTime;
        for (int i = 0; i < redpacketlist.size(); ++i) {
          RedPacket redPacket = redpacketlist.get(i);
          //更新红包的下落的位置y
          redPacket.y += (redPacket.speed * secs);

          //如果y坐标大于view的高度 说明划出屏幕,y重新设置起始位置,以及中奖属性
          if (redPacket.y > getHeight()) {
            redPacket.y = 0 - redPacket.height;
            redPacket.isRealRed = redPacket.isRealRedPacket();
          }
          //更新红包的旋转的角度
          redPacket.rotation = redPacket.rotation
              + (redPacket.rotationSpeed * secs);
        }
        invalidate();
      }
    });
    //属性动画无限循环
    animator.setRepeatCount(ValueAnimator.INFINITE);
    //属性值线性变换
    animator.setInterpolator(new LinearInterpolator());
    animator.setDuration(0);
  }

  /**
   *停止动画
   */
  public void stopRainNow() {
    //清空红包数据
    clear();
    //重绘
    invalidate();
    //动画取消
    animator.cancel();
  }

  /**
   * 开始动画
   */
  public void startRain() {
    //清空红包数据
    clear();
    //添加红包
    setRedpacketCount(count);
    prevTime = System.currentTimeMillis();
    //动画开始
    animator.start();

  }

  public void setRedpacketCount(int count) {
    if (mImgIds == null || mImgIds.length == 0)
      return;
    for (int i = 0; i < count; ++i) {
      //获取红包原始图片
      Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), mImgIds[0]);
      //生成红包实体类
      RedPacket redPacket = new RedPacket(getContext(), originalBitmap, speed,maxSize,minSize,mWidth);
      //添加进入红包数组
      redpacketlist.add(redPacket);
    }
  }

  /**
   * 暂停红包雨
   */
  public void pauseRain() {
    animator.cancel();
  }

  /**
   * 重新开始
   */
  public void restartRain() {
    animator.start();
  }

  /**
   * 清空红包数据,并回收红包中的bitmap
   */
  private void clear() {
    for (RedPacket redPacket :redpacketlist) {
      redPacket.recycle();
    }
    redpacketlist.clear();
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //获取自定义view的宽度
    mWidth = getMeasuredWidth();
  }

  @Override
  protected void onDraw(final Canvas canvas) {
    //遍历红包数组,绘制红包
    for (int i = 0; i < redpacketlist.size(); i++) {
      RedPacket redPacket = redpacketlist.get(i);
      //将红包旋转redPacket.rotation角度后 移动到(redPacket.x,redPacket.y)进行绘制红包
      Matrix m = new Matrix();
      m.setTranslate(-redPacket.width / 2, -redPacket.height / 2);
      m.postRotate(redPacket.rotation);
      m.postTranslate(redPacket.width / 2 + redPacket.x, redPacket.height / 2 + redPacket.y);
      //绘制红包
      canvas.drawBitmap(redPacket.bitmap, m, paint);
    }
  }

  @Override
  public boolean onTouchEvent(MotionEvent motionEvent) {
    switch (motionEvent.getAction()){
      case MotionEvent.ACTION_DOWN:
        //根据点击的坐标点,判断是否点击在红包的区域
        RedPacket redPacket = isRedPacketClick(motionEvent.getX(), motionEvent.getY());
        if (redPacket != null) {
          //如果点击在红包上,重新设置起始位置,以及中奖属性
          redPacket.y = 0 - redPacket.height;
          redPacket.isRealRed = redPacket.isRealRedPacket();
          if (onRedPacketClickListener != null) {
            onRedPacketClickListener.onRedPacketClickListener(redPacket);
          }
        }
        break;
      case MotionEvent.ACTION_MOVE:
        break;
      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:

        break;
    }
    return true;
  }

  //根据点击坐标点,遍历红包数组,看是否点击在红包上
  private RedPacket isRedPacketClick(float x, float y) {
    for (int i = redpacketlist.size() - 1; i >= 0; i --) {
      if (redpacketlist.get(i).isContains(x, y)) {
        return redpacketlist.get(i);
      }
    }
    return null;
  }

  public interface OnRedPacketClickListener {
    void onRedPacketClickListener(RedPacket redPacket);
  }

  private OnRedPacketClickListener onRedPacketClickListener;
  public void setOnRedPacketClickListener(OnRedPacketClickListener onRedPacketClickListener) {
    this.onRedPacketClickListener = onRedPacketClickListener;
  }
}

最后

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android辅助功能AccessibilityService与抢红包辅助

    推荐阅读:Android中微信抢红包插件原理解析及开发思路 抢红包的原理都差不多,一般是用Android的辅助功能(AccessibilityService类)先监听通知栏事件或窗口变化事件来查找红包关键字然后去模拟点击或打开红包. 下面附上源码,程序已实现自动抢红包,锁屏黑屏状态自动解锁亮屏,Android4.X测试通过.函数具体功能请看详细注释. 注:在聊天界面收到红包不会自动打开,因为通知栏没有消息提示从而监听不了,此时只需手动点一下即可.其他未知情况请自行用LogCat调试,源码已经有相

  • Android实现微信自动抢红包的程序

    简单实现了微信自动抢红包的服务,原理就是根据关键字找到相应的View, 然后自动点击.主要是用到AccessibilityService这个辅助服务,基本可以满足自动抢红包的功能,但是有些逻辑需要优化,比如,拆完一个红包后,必须手动点击返回键,才能进行下一次自动抢红包. AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="h

  • Android中微信抢红包助手的实现详解

    实现原理 通过利用AccessibilityService辅助服务,监测屏幕内容,如监听状态栏的信息,屏幕跳转等,以此来实现自动拆红包的功能.关于AccessibilityService辅助服务,可以自行百度了解更多. 代码基础: 1.首先声明一个RedPacketService继承自AccessibilityService,该服务类有两个方法必须重写,如下: /** * Created by cxk on 2017/2/3. * * 抢红包服务类 */ public class RedPack

  • 教你一步步实现Android微信自动抢红包

    本文介绍微信自动抢红包的实现方法,主要实现以下几个功能: 1.自动拆开屏幕上出现的红包 2.处于桌面或聊天列表时接收到红包信息时自动进入聊天界面并拆红包 3.日志功能,记录抢红包的详细日志 实现原理 1.利用AccessibilityService辅助服务,监测屏幕内容,实现自动拆红包的目的. 2.利用ActiveAndroid数据库简单记录红包日志 3.利用preference实现监控选项纪录 最终界面 抢红包核心代码 AccessibilityService配置 android:access

  • 分享Android微信红包插件

    本文实例为大家分享了Android微信红包插件,供大家参考,具体内容如下 效果图: 具体代码 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void handleWindowChange(AccessibilityNodeInfo nodeInfo) { switch (Stage.getInstance().getCurrentStage()) { case Stage.OPENING_STAGE: // 调试信息,打印TTL // Lo

  • Android仿百度福袋红包界面

    首先来看一下效果图: 1.编程思路 看看界面,不难发现,其就是一个放入九张图片的容器,绘制其实可以在其上面另创建一个透明View负责绘制线与圆圈.下面我们将介绍一下实现过程. ㈠自定义ViewGroup 我们知道,自定义ViewGroup一定需要实现其onLayout()方法.该方法是设置子View位置与尺寸的时候调用.还有一个onMeasure()方法,该方法是测量view及其内容来确定view的宽度和高度. ㈡存储其点与圆的位置及绘制参数 当重回界面的时候,是不会保存上一次绘制界面的内容,必

  • Android实现QQ抢红包插件

    又想到快要过年了,到时候还不知道群里要发好多红包,所以我将之前在网上宕的一份微信抢红包的代码修改了一下,实现了QQ抢红包!可以支持抢QQ拼手气红包,普通红包,口令红包,现在再也不怕20年单身手速的人跟我抢红包了! 先看测试效果图: 1.抢QQ口令红包  可以看见,只要红包一发出,自动填写口令并发出,帮你将红包抢到手! 2.抢QQ拼手气红包 拼手气红包也是一样,只要红包一发出,自动帮你把红包抢到手,是不是很爽的感觉? 3.抢QQ好友发送的红包 只要好友或者群里的人把红包一发出,就会第一时间让你抢到

  • Android微信自动抢红包插件优化和实现

    又是兴趣系列 网上有很多自动强红包的例子和代码,笔者也是做了一些优化. 先说说自己的两个个优势 1.可以在聊天界面自动强不依赖于通知栏推送 2.可以在屏幕熄灭的时候的时候点亮屏幕自动抢(目前只测过flyme) 先上图: 代码传送门: https://github.com/AndroidMsky/WXhongbao 欢迎点星星~ 原理: 通过AccessibilityService监听到状态栏通知,进行模拟点击,获取屏幕中view节点为领取红包的list并且点击最后一个. 如果不通过状态栏通知,通

  • Android中微信抢红包插件原理解析及开发思路

    一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导致了.或许是网络的原因,而且这个也是最大的原因.但是其他的不可忽略的因素也是要考虑到进去的,比如在手机充电锁屏的时候,我们并不知道有人已经开始发红包了,那么这时候也是让我们丧失了一大批红包的原因.那么关于网络的问题,我们开发者可能用相关技术无法解决(当然在Google和Facebook看来的话,他们

  • Android抢红包插件实现原理浅析

    抢红包,先看效果图~ 实现自动抢红包,解决问题有两点: 一:如何实时监听发红包的事件 二:如何在红包到来的时候自动进入页面并自动点击红包 一.如何获取红包到来的事件 为了获取红包到来状态栏的变化,我们要用到一个类:Accessibility 许多Android使用者因为各种情况导致他们要以不同的方式与手机交互. 这包括了有些用户由于视力上,身体上,年龄上的问题致使他们不能看完整的屏幕或者使用触屏,也包括了无法很好接收到语音信息和提示的听力能力比较弱的用户. Android提供了Accessibi

随机推荐