diff --git "a/Android/Blog/AndroidAdvancedSkill/Android\346\216\242\347\251\266LayoutInflater_setFactory.md" "b/Android/Blog/AndroidAdvancedSkill/Android\346\216\242\347\251\266LayoutInflater_setFactory.md" new file mode 100644 index 0000000..ad65208 --- /dev/null +++ "b/Android/Blog/AndroidAdvancedSkill/Android\346\216\242\347\251\266LayoutInflater_setFactory.md" @@ -0,0 +1,328 @@ +# Android探究LayoutInflater_setFactory +看到换肤插件里面用到,特意学习学习一下 +## 概述 +对于`LayoutInflater setFactory`,平时我们很少用到这个`API`,但是这个`API`我觉得还是有学习的必要的,能够很多意象不到的问题,准备围绕这方面编写一系列的文章。 + +本篇包含: +- setFactory 相关API介绍 +- 可能存在的问题 +- 具体的解决方案及一些实际的用途 + +## `setFactory API`学习 + +`LayoutInflater`大家肯定都不陌生,用的最多的就是其`inflate()`方法了,今天介绍的就是它的另外的两个方法: + +- `setFactory` +- `setFactory2` + +这两个方法的功能基本是一致的,`setFactory2`是在`SDK>=11`以后引入的,所以我们要根据`SDK`的版本去选择调用上述方法。 + +值得高兴的是,`v4`包下有个类`LayoutInflaterCompat`帮我们完成了兼容性的操作,提供的方法为: + +``` +LayoutInflaterCompat +setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) +``` + +好了,下面我们就来写段代码看看该方法如何使用: + +我们新建一个`Activity`,在其`onCreate`中调用`setFactory` + +``` +public class MainActivity extends AppCompatActivity +{ + private static final String TAG = "MainActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() + { + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) + { + Log.e(TAG, "name = " + name); + int n = attrs.getAttributeCount(); + for (int i = 0; i < n; i++) + { + Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i)); + } + + return null; + } + }); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } +} +``` +然后,我们运行项目,你会发现打印的`log`为(部分`log`): + +``` +MainActivity﹕ name = TextView +MainActivity﹕ layout_width , -2 +MainActivity﹕ layout_height , -2 +MainActivity﹕ text , @2131099670 +``` + +这里针对布局文件中的一个`TextView`,可以看到打印了该`TextView`的`name`以及所有的`attr`相关信息。这些信息有什么用,我们后面的文章会介绍。 + +那么这个方法能干什么呢? +从字面上理解`onCreateView`是创建`View`,那么我们修改部分代码,添加如下代码: + +``` +if (name.equals("TextView")) +{ + Button button = new Button(context, attrs); + return button; +} +``` + +运行你会发现,界面上的`TextView`变成了`Button` + +是不是很惊奇,但是我们已经能够确定这个方法的确能够根据布局文件中的信息去创建对应的`View`了。 + +你可以会问,谁没事干把`TextView`换成`Button`哇,就没什么靠谱的作用吗? +还真有,假设你的项目编写了一半,忽然有个需求需要自定义一个`TextView`(称为:`MyTextView`)来替换系统的`TextView`: + +那么现在你就不必去打开以前的布局文件把`TextView`全部进行修改,直接在`BaseActivity`里面进行下面的操作就可以了: + +``` +if (name.equals("TextView")) +{ + MyTextView view = new com.zhy.MyTextView(context,attrs); + return view; +} +``` + +对于自定义的`View`,你也可以通过比对`name`(随便设置个`name`都可以,不需要去完整的编写全路径了),然后直接去`new`出该对象。这么做有一个好处,相比系统去帮你创建,效率会高一点,因为系统有一些逻辑需要走,并且最终是通过反射的方式帮你创建`View`。 + +不过,对于`setFactory`的使用,可能会面临下面的问题。 + +现在开发`App`的时候,我们一般`Activity`都继承于`AppCompatActivity`,而在`AppCompatActivity`中,实际上也调用了`setFactory`方法。 + +如果你自己还调用了`setFactory`就可能带来一些问题,因为`setFactory`并不能重复调用。 + +## `setFactory`已经被`v7`包占领 + +打开`AppCompatActivity`的源码,找到`onCreate`,你会发现如下代码: + +``` +@Override +protected void onCreate(@Nullable Bundle savedInstanceState) { + getDelegate().installViewFactory(); + //... +} +``` +`installViewFactory`的具体实现为: + +``` +@Override +public void installViewFactory() { + LayoutInflater layoutInflater = LayoutInflater.from(mContext); + if (layoutInflater.getFactory() == null) { + LayoutInflaterCompat.setFactory(layoutInflater, this); + } else { + Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + + " so we can not install AppCompat's"); + } +} +``` + +这里你会发现,`AppCompatActivity`中也尝试去`setFactory`,如果我们再其之前调用了`setFactory`,就会打印上面的`info`信息,并且造成其`setFactroy`不会生效,其实从`Log`中也能看出: + +``` +AppCompatDelegate﹕ The Activity's LayoutInflater +already has a Factory installed +so we can not install AppCompat's +``` + +这么来看,如果我们使用`AppCompatActivity`,我们是不应该按照我们前面的方式,直接设置`setFactory`,否则我们可能会带来一些影响。 + +我们会带来什么影响呢? +其实`AppCompatActivity`的`setFactory`也是想根据`name`去生成一些类,大家还记得,更新`v7`包的时候,忽然我们的`TextView`就支持了一些属性,比如`tint`属性,以前是不支持的,怎么能够做到使用`v7`包,然后就能支持且向下兼容的呢? + +哈哈,原理就在这里,看下面的代码片段 + +``` +switch (name) { + case "TextView": + view = new AppCompatTextView(context, attrs); + break; + case "ImageView": + view = new AppCompatImageView(context, attrs); + break; + case "Button": + view = new AppCompatButton(context, attrs); + break; + case "EditText": + view = new AppCompatEditText(context, attrs); + break; + //... +} +``` + +可以看到系统其实是利用`setFactory`,瞒着我们把`TextView`等类早就换成`AppCompatTextView`等类了。 + +如果你使用`AppCompatActivty`你可以通过打印`textview.getClass()`来验证。 + +ok,看到这里,我们上面的一个问题也就知道答案了: + +我们按照前面的方式直接设置`setFactory`,会带来什么影响呢? +会造成没有办法使用一些新的特性,比如`tint`等。 + +## 解决方案 + +我们具体看下`appcompat`中`onCreateView`的全部代码: + +``` +@Override +public final View onCreateView(View parent, String name, + Context context, AttributeSet attrs) { + // First let the Activity's Factory try and inflate the view + final View view = callActivityOnCreateView(parent, name, context, attrs); + if (view != null) { + return view; + } + + // If the Factory didn't handle it, let our createView() method try + return createView(parent, name, context, attrs); +} +``` + +可以看到其最终是调用:`createView`方法完成`view`的创建,并且值得高兴的是该方法是`public`的。 + +也就是说,我们可以自己设置`factory`中,依然可以保证`appcompat`创建`View`的代码的执行。 + +``` +LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() +{ + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) + { + //你可以在这里直接new自定义View + + //你可以在这里将系统类替换为自定义View + + //appcompat 创建view代码 + AppCompatDelegate delegate = getDelegate(); + View view = delegate.createView(parent, name, context, attrs); + + return view; + } +}); +``` + +下面看一个我们常见的场景。 + +## 高效统一设置`app`中所有字体 + +很多时候我们为了`app`更加个性,然后整体采用外部引入的字体。 + +很多开发者的实现是这样的,在`BaseActivity`的`onCreate`中去从跟布局去递归遍历所有的`View`,类似的代码如下: + +``` +public void setTypeface(ViewGroup root, Typeface typeface){ + if(root==null || typeface==null){ + return; + } + int count = root.getChildCount(); + for(int i=0;i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` +其中`android:oneshot=“true”`表示该动画只播放一次,等于`false`时则循环播放。``标签定义各个帧显示的图片。显示顺序依照``定义顺序。 + +代码中使用: +``` +public class MainActivity extends Activity { + + private AnimationDrawable loadingAnimation; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.activity_main); + //将该逐帧xml文件设置为ImageView的背景 + ImageView loadingImg = (ImageView) findViewById(R.id.loading); + loadingImg.setBackgroundResource(R.drawable.loading); + loadingAnimation = (AnimationDrawable) loadingImg.getBackground(); + } + + /** + * 触摸屏幕,结束动画 + */ + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + loadingAnimation.stop(); + return true; + } + return super.onTouchEvent(event); + } + + /** + * activity显示到屏幕则开启动画 + */ + @Override + public void onWindowFocusChanged(boolean hasFocus) { + // TODO Auto-generated method stub + super.onWindowFocusChanged(hasFocus); + if (hasFocus) + loadingAnimation.start(); + } + +} +``` +[借鉴学习](http://blog.csdn.net/chziroy/article/details/40424343) diff --git "a/Android/Blog/AndroidBaseSkill/Android\345\212\250\347\224\273\344\271\213Property_Animation.md" "b/Android/Blog/AndroidBaseSkill/Android\345\212\250\347\224\273\344\271\213Property_Animation.md" new file mode 100644 index 0000000..bf5bd79 --- /dev/null +++ "b/Android/Blog/AndroidBaseSkill/Android\345\212\250\347\224\273\344\271\213Property_Animation.md" @@ -0,0 +1,70 @@ +# `Android`动画之`Property_Animation` +属性动画,顾名思义就是:沿着一定的时间顺序,通过改变`View`的属性,从而得到的动画的效果。引入属性动画最大的作用就是为了“眼见为实”,对于`ViewAnimation`,动画的移动和缩放并没有真正的改变控件的位置和热区,而属性动画则可以做到这一点。而同样重要的一点则是他的灵活性,属性动画几乎可以定义出你想要的所有动效。 + +`ValueAnimator`是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由`ValueAnimator`这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给`ValueAnimator`,并且告诉它动画所需运行的时长,那么`ValueAnimator`就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,`ValueAnimator`还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。 +首先来认识下他的相关类。 + +`Interface` +- `Animator.AnimatorListener`: 动画监听器从一个动画接收通知消息。 +- `Animator.AnimatorPauseListener`:` A pause listener receives notifications from an animation when the animation is paused or resumed.` +- `LayoutTransition.TransitionListener`: 此接口用来监听转变的开始事件与结束事件。 +- `TimeAnimator.TimeListener`: 此接口实现可将自身设置为`TimeAnimator`实例的更新监听器并接受回调,在每一帧上获得自动画开始以来的时间和自上一帧以来的时间间隔。 +- `TimeInterpolator`: 时间内插器定义了一个动画的变化的速率。 +- `TypeEvaluator`: 使用setEvaluator(TypeEvaluator)功能需要的接口。 +- `ValueAnimator.AnimatorUpdateListener`: 动画每一帧都会对此接口进行回调 +- `ValueAnimator`: 实例的更新监听器并接受回调, 当已经为该ValueAnimator计算出当前帧的值以后。 + +`Class` + +- `Animator`: 是那些具有启动、结束且包含`AnimatorListeners`的基础支持功能的类的超级类。 +- `AnimatorInflater`: 此类用来将Animator XML文件实例化并注入`Animator`类。 +- `AnimatorListenerAdapter`: 此适配器为`Animator.AnimatorListener`的方法提供了空的实现。 +- `AnimatorSet`: 此类将一组Animator对象按指定的顺序播放。 +- `AnimatorSet.Builder`: 为方便向`AnimatorSet`加入`animation`对象(连同不同`animation`对象之间的关系)工具类。 +- `ArgbEvaluator`: 此计算器可作为类型内插器,于代表`ARGB`颜色的整型值之间。 +- `FloatEvaluator`: 此计算器可作为类型内插器,于浮点型数值之间。 +- `IntEvaluator`: 此计算器可作为类型内插器,于整型数值之间。 +- `Keyframe`: 该类包含了一个`animation`类的`time/value`值对。 +- `LayoutTransition`: 该类在 `ViewGroup`类中使得背景可以是自动的动画。 +- `ObjectAnimator`: `ValueAnimator`的子类,提供了对目标对象的属性动画的支持。 +-` PropertyValuesHolder`: 该类载有一个属性的信息,以及此属性在动画过程需要用到的值。 +- `RectEvaluator`: `This evaluator can be used to perform type interpolation between Rect values`. +- `TimeAnimator`: 该类为已经与系统中所有其他动画对象同步的监听器提供一个简单的回调机制。 +- `ValueAnimator`: 该类为运行中的动画对象提供一个简单的时间引擎,以便计算动画的值并将结果设置于目标对象。 + +这里要着重记录的是这几个类: +- `ValueAnimator` 属性动画的核心类 +- `ObjectAnimator` 继承 `ValueAnimator` 对`ValueAnimator`进行了一层封装 +- `AnimatorSet` 可以同时运行一组动画 +- `PropertyValuesHolder` 他代表一个在动画运行中需要过度到的值。 +- `TypeEvaluator` 实现此接口的实例,将决定`AnimatorUpdateListener`接收到的值。 + +这里有必要说明一下,上面对动画的描述是“运行”,而不是“播放”。因为属性动画的本质是在指定的时间内于指定的值之间过度。这就意味着他并不仅限于`View`控件。举例来说,他可以是一个不断运动的看不见的点,而你在需要的时候可以通过回调知道在某一时间点对应的值,从而进行`canvas`的绘制。 + +## `ObjectAnimator` +``` +private void runAnimator(View view) { + //旋转 + String rotationX = "rotationX"; + String rotationY = "rotationY"; + //渐变 + String alpha = "alpha"; + //缩放 + String scale = "scale"; + String scaleX = "scaleX"; + String scaleY = "scaleY"; + //移动 + String translationX = "translationX"; + String translationY = "translationY"; + + ObjectAnimator.ofFloat(view, rotationX, 0.0f, 360f) + .setDuration(2000) + .start(); +} +``` +`ObjectAnimator` 提供了`ofInt`、`ofFloat`、`ofObject` + +他们都是设置(动画作用的元素、动画名称、动画开始、结束、以及中间的任意个属性值),这就意味着可以传任意多个值,动画会依次作用在对应的值上。 + +可以看出,他除了是以动画方式改变`View`形态之外,跟`ViewAnimator`没什么区别了。 +他可以看做是`Animator`家族中的上层了,被封装过,使用简单,又兼备了属性动画的特性, diff --git "a/Android/Blog/AndroidBaseSkill/Android\345\212\250\347\224\273\344\271\213View_Animation.md" "b/Android/Blog/AndroidBaseSkill/Android\345\212\250\347\224\273\344\271\213View_Animation.md" new file mode 100644 index 0000000..5013adb --- /dev/null +++ "b/Android/Blog/AndroidBaseSkill/Android\345\212\250\347\224\273\344\271\213View_Animation.md" @@ -0,0 +1,84 @@ +# `Android`动画之`View_Animation` +`View Animation` 最简单,只支持简单的缩放、平移、旋转、透明度基本的动画。 +## `xml`方式使用 +### `Alpha`(渐变) +``` + + + + +``` +### `rotate`(旋转) +- `fromDegrees`: 开始的角度 +- `toDegrees`: 结束的角度,`+`表示是正的 +- `pivotX`: 用于设置旋转的`x`轴坐标 + - 当值为`50`, 表示使用绝对位置定位 + - 当值为`50%`, 表示使用相对于控件本身定位 + - 当值为`50%p`, 表示使用相对于控件的父控件定位 +- `pivotY`: 用于设置旋转是的`y`轴坐标 + +``` + + + + +``` +### `scale`(缩放) +``` + + + + +``` +### `translate`(位移) +``` + + + + +``` + +代码中调用: +``` +Animation animation = AnimationUtils.loadAnimation(this, R.anim.alpha_test); +menu.startAnimation(animation); +``` +## 直接用代码创建动画 +``` +TranslateAnimation tAnim; +if (swipe_tag == SHOW_MENU) { + tAnim = new TranslateAnimation(0, max_menu_margin - latestMargin, 0, 0); + tAnim.setInterpolator(new DecelerateInterpolator()); + tAnim.setDuration(800); + tAnim.setFillAfter(true); + menu.startAnimation(tAnim); +} else { + tAnim = new TranslateAnimation(0, min_menu_margin - latestMargin, 0, 0); + tAnim.setDuration(800); + tAnim.setFillAfter(true); + menu.startAnimation(tAnim); +} +``` diff --git "a/Android/Blog/AndroidBaseSkill/Android\345\212\250\347\224\273\344\271\213\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Android/Blog/AndroidBaseSkill/Android\345\212\250\347\224\273\344\271\213\345\237\272\347\241\200\347\237\245\350\257\206.md" new file mode 100644 index 0000000..1f47df2 --- /dev/null +++ "b/Android/Blog/AndroidBaseSkill/Android\345\212\250\347\224\273\344\271\213\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -0,0 +1,13 @@ +# `Android`动画之基础知识 +`Android`动画主要分为三大类 +- `View Animation` +- `Drawable Animation` +- `Property Animation` + +其中`Drawable Animations`对大多数人来说是三者中最容易理解的,其实它就是很多书籍中提到的逐帧动画(`frame-by-frame animation`)。而`Property Animation`和`View Animation`是相对比较容易混淆的,下面先讲解二者的区别。 + +`View Animation`有两个缺点:(1)`View Animation`一般只能修改组件(`View Object`)的部分属性,比如:`scaling`(大小)和`rotation`(旋转),但是无法修改组件的背景颜色。(2)`View Animation`使某个组件产生动画效果移动一段距离后,比如从屏幕左侧移动到右侧,其实整个过程是绘制出来的效果,该组件真正的位置依然保留在左侧,只有点击左侧位置才能触发该组件。所以想真正移动某组件,需要在动画结束后添加代码实现。 + +`Property Animation`则没有以上`View Animation`的两个限制,`Property Animation`可以修改任何对象(`View Object` 或者 `non-view Object`)的任何属性,比如大小,旋转,颜色。并且,移动后的组件,位置也回跟随着改变。 + +`Android`官网推荐使用`Property Animation`,但是`View Animation`也有其优点:使用方便简单,所以当`View Animation`能方便快速地解决需求时,选择它也是不错的选择。 diff --git a/Android/Blog/AndroidBaseSkill/README.md b/Android/Blog/AndroidBaseSkill/README.md index ca0e60c..0be22d9 100644 --- a/Android/Blog/AndroidBaseSkill/README.md +++ b/Android/Blog/AndroidBaseSkill/README.md @@ -1,5 +1,9 @@ # Android 基本技能的使用 -* ListView悬浮头部实践 -* PullToRefreshListView头部悬浮效果实践 -* TabLayout+ViewPage_简单实现App底部Tab布局 -* Android屏幕适配及DisplayMetrics解析 +* `ListView`悬浮头部实践 +* `PullToRefreshListView`头部悬浮效果实践 +* `TabLayout+ViewPage_`简单实现`App`底部`Tab`布局 +* `Android`屏幕适配及`DisplayMetrics`解析 +* `Android`动画之`View_Animation` +* `Android`动画之`Drawable_Animation` +* `Android`动画之基础知识 +* `Android`动画之`Property_Animation`