动画之复习

一、动画

1.动画的种类:

  • tween 补间动画。通过指定View的初末状态和变化方式,对View的内容完成一系列的图形变换来实现动画效果。 Alpha, Scale ,Translate, Rotate。
  • frame 帧动画。AnimationDrawable控制animation-list.xml布局
  • PropertyAnimation 属性动画3.0引入,核心思想是对值的变化。

原理及特点:
1.属性动画:
插值器:作用是根据时间流逝的百分比来计算属性变化的百分比
估值器:在1的基础上由这个东西来计算出属性到底变化了多少数值的类
其实就是利用插值器和估值器,来计出各个时刻View的属性,然后通过改变View的属性来实现View的动画效果。
2.View动画:
只是影像变化,view的实际位置还在原来地方。
3.帧动画:
是在xml中定义好一系列图片之后,使用AnimatonDrawable来播放的动画。
区别?
属性动画才是真正的实现了view的移动,补间动画对view 的移动更像是在不同地方绘制了一个影子,实际对象还是处于原来的地方。 当动画的 repeatCount 设置为无限循环时,如果在Activity退出时没有及时将动画停止,属性动画会导致Activity无法释放而导致内存泄漏,而补间动画却没问题。 xml 文件实现的补间动画,复用率极高。在 Activity切换,窗口弹出时等情景中有着很好的效果。 使用帧动画时需要注意,不要使用过多特别大的图,容导致内存不足。
为什么属性动画移动后仍可点击?
播放补间动画的时候,我们所看到的变化,都只是临时的。而属性动画呢,它所改变的东西,却会更新到这个View所对应的矩阵中,所以当ViewGroup分派事件的时候,会正确的将当前触摸坐标,转换成矩阵变化后的坐标,这就是为什么播放补间动画不会改变触摸区域的原因了。

属性动画和补间动画的区别?
1)、作用对象不同,补间动画只能作用在view上,属性动画可以作用在所有对象上。
2)、属性变化不同,补间动画只是改变显示效果,不会改变view的属性,比如位置、宽高等,而属性动画实际改变对象的属性。
3)、动画效果不同,补间动画只能实现位移、缩放、旋转和透明度四种动画操作,而属性动画还能实现补间动画所有效果及其他更多动画效果。

二、属性动画(PropertyAnimation)

通过修改对象的属性来产生的动画,叫属性动画。内部通过插值器来计算运动规律,再结合估值器给出一个确定的值,再通过对象的set方法来修改其属性。
从本质上来讲,属性动画与补间动画没有什么区别,不同在于属性动画真实地修改了对象的属性,而补间动画只修改了对象的展示效率,并未修改其属性。
如何使用:  ObjectAnimator.ofInt(tv1, "translationX", 0, 500).setDuration(1000).start();
属性动画:
补间动画增强版,支持对对象执行动画。 (0,0) -> (100,100);
是补间动画的增强升级版,之所以提出属性动画,原因是补间动画有一些缺点,补间动画主要是:
• 补间动画只能作用域某个View视图,使用受限。
• 只改变view视图效果,无法改变真实属性。
• 只能实现某个单一效果
相较于补间动画,属性动画的使用范围不在局限于view,同时还可以根据需要实现各种效果。
属性动画:实际是一种不断的对值进行操作的机制,并将值赋给到指定对象的指定属性上,可以是任意对象的任意属性。

3.1、Animator及子类
Animator是属性动画的基类,是一个抽象类。该抽象类有两个重要的具体实现类,分别是ValueAnimator和ObjectAnimator类(继承ValueAnimator)。另外Evaluator,AnimatorSet等类。Evaluator称之为估值器,其作用类似于之前的插值器。
插值器: 即影响动画的播放速度。
插值器主要是用来定义动画变化过程中的变化速率的一个工具。在android中提供很多的插值器,比如:
• AccelerateInterpolator:加速,开始时慢中间加速
• DecelerateInterpolator: 减速,开始时快然后减速
• AccelerateDecelerateInterolator:先加速后减速,开始结束时慢,中间加速
• AnticipateInterpolator:反向,先向相反方向改变一段再加速播放
• LinearInterpolator:线性,线性均匀改变,最常用的插值器类型。
AnimatorSet主要用于实现多种动画的组合,形成组合动画。常见的方法:
• play:播放动画
• after:将现有动画延迟x毫秒后执行
• with:将现有动画和传入的动画同时执行
• after:将现有动画插入到传入的动画之后执行
• before:将现有动画插入到传入的动画之前执行

3.2、ValueAnimator
将作用对象的属性值从初始值以整数型数值的形式过渡到结束值。有两种实现方式:代码形式和xml文件形式。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<animator
android:valueFrom="0" <!--初始值-->
android:valueTo="100" <!--结束值-->
android:valueType="intType" <!--变化值类型 :floatType & intType-->
android:duration="3000" <!--动画持续时间(ms),必须设置,动画才有效果-->
android:startOffset ="1000"
android:fillBefore = "true"
android:fillAfter = "false"
android:fillEnabled= "true"
android:repeatMode= "restart"
android:repeatCount = "0"
android:interpolator="@android:anim/accelerate_interpolator"/>
<!--动画延迟开始时间(ms)-->
<!--动画播放完后,视图是否会停留在动画开始的状态,默认为true-->
<!--动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false-->
<!--是否应用fillBefore值,对fillAfter值无影响,默认为true-->
<!--选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|-->
<!--重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复-->
<!--插值器,即影响动画的播放速度,下面会详细讲-->
</set>

3.3、ObjectAnimator
ObjectAnimator的原理是直接对对象的属性值进行改变操作,从而实现动画效果。该类继承ValueAnimator类,即底层动画实现机制是基于ValueAnimator类 。
以xml形式定义为例:注意:以XML方式,res的文件夹名称必须是animator,否则无法引用。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<ObjectAnimator
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:duration = "800"
android:propertyName="alpha"/>
</set>

3.4、ValueAnimator与ObjectAnimator区别?
• ValueAnimator 类是先改变值,然后手动赋值 给对象的属性从而实现动画;是间接对对象属性进行操作;
• ObjectAnimator 类是先改变值,然后自动赋值 给对象的属性从而实现动画;是直接对对象属性进行操作;

差值器与估值器关系?
差值器与估值器合作关系。 差值器决定了变化规则,即如何变化,如加速度;估值器决定了变化的具体值。
1)差值器:
控制动画开始值过渡到结束值变化规律的接口。如加速变化、减速变化等。
使用差值器,可在动画的xml里配置android:interpolator,或在代码用setInterpolator()进行配置。
创建差值器需要使用代码实现,创建一个插件器类实现Interpolator接口
public interface Interpolator extends TimeInterpolator {} 

public interface TimeInterpolator {
    float getInterpolation(float input);
}
系统插值器有以下几种
类名  对应资源  作用
LinearInterpolator  @android:anim/linear_interpolator  匀速运行
AccelerateInterpolator  @android:anim/accelerate_interpolator  动画加速运行
DecelerateInterpolator  @android:anim/decelerate_interpolator  减速运行
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator  先加速再减速运行
OvershootInterpolator

AnticipateInterpolator @android:

AnticipateOvershootInterpolator 

BounceInerpolator

CycleInterpolator 

2)估值器:
控制动画从开始值过渡到结束值时具体的数据值接口。需要注意是估值器只针对属性动画。使用估值器时,需要需要调用setEvaluator将估值器设置进去即可;实现估值器,也需要使用代码来实现TypeEvaluator接口。
public interface TypeEvaluator<T> {
  public T evaluate(float fraction, T startValue, T endValue);
}
3.5 属性动画的实现方式?
ValueAnimator anim = ValueAnimator.ofFloat(Øf, 1f);
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@0verride
public void onAnimationUpdate(ValueAnimator animation) {
      float currentValue = (float) animation. getAnimatedValue();
}});
anim.start() ;
1.平移动画:
ObjectAnimator.ofFloat(view, "translationX", 0f, 200f).setDuration(500).start();
2.透明度动画
ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).setDuration(500).start();
3.缩放动画:
ObjectAnimator.ofFloat(view, "scalex", 1f, 0f).setDuration(500).start();
4.旋转动画
ObjectAnimator.ofFloat(view, "rotation", 0f, 360f).setDuration(500).start();
5.位移动画
ObjectAnimator.ofFloat(view, "translationX", 0f, view.width as float).setDuration(500).start();
6.背景颜色渐变:
if (Build.VERSION. SDK_ INT >= Build. VERSION_ CODES.M) [
// API必須代碍23オ能使用ofArgb
val animator = ObjectAnimator.ofArgb(view, "backgroundColor", -0xff01, -Øx100 ,-0xff01)
 animator.duration = 4000
 animator .start()
}

7.组合动画:缩放、透明度动画先后开始
fun animatorSet(view : View){
val animatorSet = AnimatorSet()
val scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 2f)
val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 2f)
val alpha = ObjectAnimator . ofFloat(view, "alpha", 1f, 0f)
val scaleX2 = ObjectAnimator.ofFloat(view, "scaleX", 2f, 0f)
val scaleY2 = ObjectAnimator.ofFloat(view, "scaleY",2f, 0f)
val rotation= ObjectAnimator.ofFloat(view, "rotation" , 0f, -360f)
animatorSet.duration = 5000
animatorSet.interpolator = DecelerateInterpolator
animatorSet.play(scaleX)
.with(scaleY)/ /将现有动画和传入的动画同时执行
. before(alpha)// 将现有动画插入到传入的动画之前执行
. before(scaleX2)
. before(scaleY2) //alpha 动画后执行
. after(rotation) //将现有动画插入到传入的动画之后执行
. after(1000) //将现有动画延迟指定毫秒后执行
//在动画执行前添加监听事件
animatorSet . addLi stener(object : Animator.AnimatorListener{
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationEnd animation: Animator) {}
override fun onAnimationCancel animation: Animator) {}
override fun onAnimationRepeat animation: Animator) {}
animatorSet. start()
.after(Animator anim) :将现有动画插入到传入的动画之后执行
.after(long delay) :将现有动画延迟指定毫秒后执行
.before(Animator anim):将现有 动画插入到传入的动画之前执行
.with(Animator anim) :将现有动画和传入的动画同时执行

8、按钮控件宽度增加动画
fun setWh(view : View){
 val animator: ObjectAnimator =
 ObjectAnimator.ofInt(view ,"width" , view.width, view.width*2)
 animator.duration = 1000
 animator.start()
}  

3.6 属性动画概述:
View动画通过对场景里的对象不断做图像变化(平移、缩放、旋转、透明度)从而产生动画效果,View动画支持自定义;
属性动画通过动态的改变对象的属性从而达到动画效果,属性动画为API 11的新特性;
属性动画可以对任意对象的属性进行动画而不仅仅是View,在一个时间间隔内可以完成对象从一个属性值到另一个属性值的改变。
因此属性动画几乎是无所不能的,只要对象有这个属性,它都能实现动画效果。
使用:
属性动画中有ValueAnimator 、ObjectAnimator(继承ValueAnimator) 和 AnimatorSet等概念,
其中ObjectAnimator用来是实现一个属性动画,AnimatorSet 是动画集合,定义一组动画。
属性动画可通过代码实现和XML来定义,属性动画需要定义在res/animator目录下,其中的set、objectAnimator 和 animator标签分别对应 AnimatorSet、ObjectAnimator 和 ValueAnimator 。
尽管属性动画可以通过XML来实现,但实际开发中往往会采用代码来实现,因为很多时候一个属性的起始值是无法提前确定的。

插值器和估值器区别?
TimeInterpolator时间插值器:它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator (线性插值器:匀速动画)、 AccelerateDecelerateInterpolator (加速减速插值器:动画两头慢中间快)和 DecelerateInterpolator (减速插值器:动画越来越慢)等。
TypeEvaluator译为类型估值算法,也叫估值器

它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有IntEvaluator(针对整型属性)、 FloatEvaluator (针对浮点型属性)和ArgbEvaluator(针对Color属性)。属性动画中的插值器(Interpolator)和估值器(TypeEvaluator )很重要,它们是实现非匀速动画的重要手段。

属性动画的监听器:
属性动画提供了监听器来监听动画的播放过程,主要有AnimatorUpdateListener和AnimatorListener。
AnimatorListener 可以监听动画的开始、结束、取消以及重复播放 ,同时为了方便开发,系统还提供AnimatorListenerAdapter类,他是AnimatorListener的适配器类,可以有选择的实现这四个监听方法。
AnimatorUpdateListener比较特殊,他会监听整个动画过程,动画是由许多帧组成的,每播放一帧对应监听方法就会被调用一次。
对任意属性做动画:
属性动画可以对任意属性添加动画效果,假如我们要给一个 button 加一个动画,让这个 button 的宽度增加100px,可能会这样写:
@Override
public void onClick(View v){
     ObjectAnimator.ofInt(v,"width",100).setDuration(1000).start();
}
但是当点击按钮后并没有效果,不是说好的可以对任意属性实现动画嘛,其实没效果是对的,
下面分析属性动画的原理
属性动画要求动画作用的对象提供该属性的 get和set方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set 方法的值都不一样,确切的来说是随着时间的推移,所传递的值越来越接近最终值。
总结一下,我们对object的属性abc 做动画,如果想让动画生效,要同时满足以下两个条件:
1.object必须提供setAbc方法,如果动画的时候没有传递初始值,那么还要提供getAbc方法,因为系统要去取abc属性的初始值(若这条不满足,程序直接 Crash)
2.object的setAbc对属性abc所做的改变必须能够通过某种方法反映出来,比如会带来UI的改变之类的(如果这条不满足,动画无效果但不会Crash)
这跟 DataBinding 中的思想是一样的,这里的object对应于DataBinding中的 ViewModel,ViewModel 的成员变量set方法中需要提醒DataBinding去更新 view 容,如下:
public void setName(String name) {
  this.name = name;
  notifyPropertyChanged(BR.name);
}
针对以上问题,官方文档告诉我们有3种解决方法:
给你的对象加上get和set方法,如果有权限的话用一个类来包装原始对象,间接为其提供get和set方法,采用ValueAnimator监听动画过程,自己实现属性的改变

3.7  Property Animation 动画有两个步聚:

1.计算属性值

2.为目标对象的属性设置属性值,即应用和刷新动画

image

计算属性分为3个过程:

过程一:

计算已完成动画分数 elapsed fraction。为了执行一个动画,你需要创建一个ValueAnimator,并且指定目标对象属性的开始、结束和持续时间。在调用 start 后的整个动画过程中,ValueAnimator 会根据已经完成的动画时间计算得到一个0 到 1 之间的分数,代表该动画的已完成动画百分比。0表示 0%,1 表示 100%。

过程二:

计算插值(动画变化率)interpolated fraction 。当 ValueAnimator计算完已完成的动画分数后,它会调用当前设置的TimeInterpolator,去计算得到一个interpolated(插值)分数,在计算过程中,已完成动画百分比会被加入到新的插值计算中。

过程三:

计算属性值当插值分数计算完成后,ValueAnimator会根据插值分数调用合适的 TypeEvaluator去计算运动中的属性值。 以上分析引入了两个概念:已完成动画分数(elapsed fraction)、插值分数( interpolated fraction )。

3、帧动画Frame Animation:   通过顺序播放排列好的图片来实现,类似电影、gif。

帧动画通过顺序播放一系列图像产生动画效果,简单理解为图片切换动画,图片过大会导致OOM;
逐帧动画经常使用XML资源文件方式进行定义和声明:
• animation-list:xml文件根节点的标签名,表示逐帧动画。item表示每一帧的资源内容。
• android:oneshot:该属性用来控制动画是否循环播放,true表示不会循环播放,false表示会循环播放。
• android:duration:该属性表示每一帧持续播放的时间。

2、补间动画(TweenAnimation)
补间动画只需要定义动画开始与结束两个关键帧,并指定动画变化的时间与方式等 。
补间动画:设定动画的几个关键点,其他点使用系统计算来完成的动画。

可以使视图组件移动、放大、缩小以及产生透明度的变化。


缺点:


1.补间动画是只能够作用在View上


2.只能够实现移动、缩放、旋转和淡入淡出这四种动画操作


3.只是改变了View的显示效果而已,而不会真正去改变View的属性
主要有四种基本的效果:透明度、缩放、位移、旋转。
1) 在Java代码中,分别AlphaAnimation,ScaleAnimation(缩放动画),TranslateAnimation(平移动画), RotateAnimation(旋转动画)。
透明度动画:
AlphaAnimation alpha = new AlphaAnimation(0, 1);
alpha.setDuration(500); //设置持续时间
alpha.setFillAfter(true); //动画结束后保留结束状态
alpha.setInterpolator(new AccelerateInterpolator()); //添加差值器
ivImage.setAnimation(alpha);

2) 在xml文件形式定义时,xml文件中标签名分别如下所示(但一般建议直接使用xml,更方便、代码也简洁)
• alph:渐变透明度动画效果
• scale: 渐变尺寸伸缩动画效果
• translate:画面转换位置移动动画效果
• rotate:画面转移旋转动画效果

在使用时加载xml动画,生成动画对象
Animation animation = AnimationUtils.loadAnimation(getContext(),R.anim.alpha);
animation.setDuration(1000);
image.startAnimation(animation);

差值器:
插值器主要是用来定义动画变化过程中的变化速率的一个工具。在android中提供了很多类型的插值器,比如:
• AccelerateInterpolator:加速,开始时慢中间加速
• DecelerateInterpolator: 减速,开始时快然后减速
• AccelerateDecelerateInterolator:先加速后减速,开始结束时慢,中间加速
• AnticipateInterpolator:反向,先向相反方向改变一段再加速播放
• LinearInterpolator:线性,线性均匀改变,最常用的插值器类型。

2.1、补间动画
一句话补间动画:只能给View加,不能给对象加,并且不会改变对象的真实属性。
无需关注每一帧,只需要定义动画开始与结束两个关键帧,并指定动画变化的时间与方式等 。
主要有四种基本的效果: 透明度变化,大小缩放变化,位移变化,旋转变化
可以在xml中定义,也可以在代码中定义。
透明度的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<alpha
android:duration="1000"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>

缩放的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<scale
android:duration="1000"
android:fillAfter="false"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.4"
android:toYScale="1.4" />
</set>

平移的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="2000"
android:fromXDelta="30"
android:fromYDelta="30"
android:toXDelta="-80"
android:toYDelta="300" />
</set>

旋转的定义:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<rotate
android:duration="3000"
android:fromDegrees="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="+350" />
</set>

代码方式的补间动画(推荐):
透明度定义:
AlphaAnimation alpha = new AlphaAnimation(0, 1);
alpha.setDuration(500); //设置持续时间
alpha.setFillAfter(true); //动画结束后保留结束状态
alpha.setInterpolator(new AccelerateInterpolator()); //添加差值器
ivImage.setAnimation(alpha);
缩放定义:
ScaleAnimation scale = new ScaleAnimation(1.0f, scaleXY, 1.0f, scaleXY, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scale.setDuration(durationMillis);
scale.setFillAfter(true);
ivImage.setAnimation(scale);

平移定义
TranslateAnimation translate = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);
translate.setDuration(durationMillis);
translate.setFillAfter(true);
ivImage.setAnimation(translate);

旋转定义:
RotateAnimation rotate = new RotateAnimation(fromDegrees, toDegrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(durationMillis);
rotate.setFillAfter(true);
ivImage.setAnimation(rotate);

// 动画集合
AnimationSet set = new AnimationSet(true);
set.addAnimation(animRotate);
set.addAnimation(animScale);
set.addAnimation(animAlpha);
// 启动动画
rlRoot.startAnimation(set);
set.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
// 动画结束,跳转页面
// 如果是第一次进入, 跳新手引导 否则跳主页面
}
});

1、帧动画(FrameAnimation)
通过播放一帧一帧的图片组成动画。可在xml添加每一帧图片,也可通过代码来添加每一帧图片。帧动画只针对ImageView对象。推荐使用一些小图片,它的性能不是很好,如果使用大图帧动画,会出现性能问题导致卡顿。

步骤:
1)将图片放到res/drawable
2) 在drawable文件夹下定义动画的xml文件 或 也可采用编码方式AnimatonDrawable类定义动画

• 使用xml添加帧图片
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/compositedst1" android:duration="500"></item>
<item android:drawable="@drawable/compositedst2" android:duration="500"></item>
<item android:drawable="@drawable/compositedst3" android:duration="500"></item>
<item android:drawable="@drawable/compositedst4" android:duration="500"></item>
</animation-list>

//使用xml方式添加帧图片
imageView.setImageResource(R.drawable.animationlist);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();

animationDrawable.start();

• 代码添加帧图片
AnimationDrawable animationDrawable = new AnimationDrawable();
animationDrawable.setOneShot(false);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.compositedst1),1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.compositedst2),1000);

//使用代码方式添加帧图片
AnimationDrawable animationDrawable = new AnimationDrawable();
animationDrawable.addFrame(xxx);
animationDrawable.addFrame(xxx);

animationDrawable.start();

1.1、帧动画
比较常用的方式,在res/drawable目录下新建动画XML文件:
设置或清除动画代码:
mIvRefreshIcon.setImageResource(R.drawable.anim_loading);
mAnimationDrawable = (AnimationDrawable) mIvRefreshIcon.getDrawable();
mAnimationDrawable.start();
//停止动画
mIvRefreshIcon.clearAnimation();
if (mAnimationDrawable != null){
  mAnimationDrawable.stop();
}
设置Background和设置ImageResource是一样的效果:
ImageView voiceIcon = new ImageView(CommUtils.getContext());
voiceIcon.setBackgroundResource(R.drawable.right_voice );
AnimationDrawable frameAnim = (AnimationDrawable) voiceIcon.getBackground();
frameAnimatio.start();

posted on 2022-09-19 11:51  左手指月  阅读(107)  评论(0编辑  收藏  举报