主目錄見(jiàn):Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)
一.目標(biāo)
看了上一篇《換膚框架(一)之Support v7庫(kù)解析》想必大家很期待換膚框架(二),但是為什么冒死把這一篇提前呢某弦?這里有個(gè)原因就是為了給大家鞏固下support v7里面的知識(shí)點(diǎn)刀崖,以便到時(shí)能更容易理解換膚框架亮钦,如果你現(xiàn)在去看我等你十分鐘蜂莉。映穗。蚁滋。
大家想要源代碼可以重?fù)粝螺d睦霎,當(dāng)然這也不是原創(chuàng)代碼了,但是這個(gè)地方可以用來(lái)說(shuō)明這個(gè)知識(shí)點(diǎn)副女。
二.代碼分析
1.基礎(chǔ)用法
這個(gè)地方用法很簡(jiǎn)單碑幅,直接看下面使用的代碼:
1.1)第一步:在布局里面添加
<com.lenovohit.redbookparallx.ParallxContainer
android:id="@+id/parallax_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
1.2)第二步:給這個(gè)ParallxContainer 設(shè)置頁(yè)面
ParallxContainer container = (ParallxContainer) findViewById(R.id.parallax_container);
container.setUp(
new int[]{
R.layout.view_intro_1,
R.layout.view_intro_2,
R.layout.view_intro_3,
R.layout.view_intro_4,
R.layout.view_intro_5,
R.layout.view_login
}
);
//設(shè)置動(dòng)畫(huà)
ImageView iv_man = (ImageView) findViewById(R.id.iv_man);
iv_man.setBackgroundResource(R.drawable.man_run);
container.setIv_man(iv_man);
但是這個(gè)地方布局里面會(huì)有自定義的屬性,舉一個(gè)頁(yè)面view_intro_1說(shuō)明一下:
<ImageView
android:id="@+id/iv_0"
android:layout_width="103dp"
android:layout_height="19dp"
android:layout_centerInParent="true"
android:src="@mipmap/intro1_item_0"
app:x_in="1.2"
app:x_out="1.2" />
大家看下第一個(gè)頁(yè)面里面的一個(gè)ImageView添加了 app:x_in拷窜, app:x_out涧黄,這里ImageView原本是沒(méi)有這個(gè)屬性的笋妥,那么我們要怎么去識(shí)別這些屬性并且能設(shè)置給ImageView呢春宣?答案就是我們會(huì)自定義一個(gè)繼承LayoutInflater的類(lèi)去攔截View的創(chuàng)建過(guò)程。如果不知道這個(gè)知識(shí)點(diǎn)的可以倒帶到《換膚框架(一)之Support v7庫(kù)解析》里面講的非常清楚嚷辅。
2.自定義LayoutInflater
自定義LayoutInflater的話要去繼承LayoutInflater簸搞,然后會(huì)強(qiáng)制你實(shí)現(xiàn)他的構(gòu)造方法和cloneInContext(Context context)方法趁俊。
?1.首先我們看下cloneInContext(Context context)方法:
@Override
public LayoutInflater cloneInContext(Context context) {
return new ParallaxLayoutInflater(this,context,fragment);
}
我們看到這個(gè)方法就是返回一個(gè)LayoutInflater 對(duì)象寺擂,所以我們把我們的自定義LayoutInflater對(duì)象返回即可般卑。
?2.然后我們看下構(gòu)造方法:
protected ParallaxLayoutInflater(LayoutInflater original, Context newContext,ParallaxFragment fragment) {
super(original, newContext);
this.fragment = fragment;
//重新設(shè)置布局加載器的工廠
//工廠:創(chuàng)建布局文件中所有的視圖
LayoutInflaterCompat.setFactory(this, new ParallaxFactory(this));
}
我們看到這里有一句關(guān)鍵代碼蝠检,就是調(diào)用LayoutInflaterCompat的setFactory方法叹谁,其實(shí)在LayoutInflater中就有setFactory方法,為什么這個(gè)地方要調(diào)用LayoutInflaterCompat的setFactory方法呢析苫?答案是兼容性衩侥,這個(gè)類(lèi)是兼容包support里面的,不然在繼承了AppCompatActivity之后會(huì)沒(méi)有效果矛物。
這個(gè)地方重新提下為什么要設(shè)置Factory峦萎,原因很簡(jiǎn)單就是setContentView()源碼分析里面有句話:
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
可以看出只要我們?cè)O(shè)置了我們自定義的Factory然后重寫(xiě)onCreateView方法就可以攔截View的創(chuàng)建過(guò)程了爱榔,這樣就可以攔截到前面說(shuō)的ImageView沒(méi)有的 app:x_in详幽, app:x_out屬性悴能,達(dá)到設(shè)置給ImageView的目的漠酿。
3.自定義factory
前面我們看到我們要設(shè)置一個(gè)自己的Factory炒嘲,那么這個(gè)Factory怎么自定義呢?首先第一步要繼承LayoutInflaterFactory,然后重寫(xiě)onCreateView方法夭拌,這也是前面說(shuō)過(guò)的鸽扁。方法如下:
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = null;
if (view == null) {
view = createViewOrFailQuietly(name,context,attrs);
}
//實(shí)例化完成
if (view != null) {
//獲取自定義屬性,通過(guò)標(biāo)簽關(guān)聯(lián)到視圖上
setViewTag(view,context,attrs);
fragment.getViews().add(view);
}
return view;
}
我們看到createViewOrFailQuietly這一句笆呆,這個(gè)方法就是創(chuàng)建一個(gè)View,方法是我們自己實(shí)現(xiàn)的东囚,方法如下:
private View createViewOrFailQuietly(String name, Context context,
AttributeSet attrs) {
//1.自定義控件標(biāo)簽名稱(chēng)帶點(diǎn),所以創(chuàng)建時(shí)不需要前綴
if (name.contains(".")) {
createViewOrFailQuietly(name, null, context, attrs);
}
//2.系統(tǒng)視圖需要加上前綴
for (String prefix : sClassPrefix) {
View view = createViewOrFailQuietly(name, prefix, context, attrs);
if (view != null) {
return view;
}
}
return null;
}
方法的意思就是
1.如果是自己自定義的控件的話就不用加上前綴桨嫁,為什么呢植兰?原因是因?yàn)槲覀冏远x控件寫(xiě)進(jìn)xml文件的時(shí)候是包名+類(lèi)名例如com.lenovohit.redbookparallx.ParallxContainer,這樣在系統(tǒng)反射創(chuàng)建這個(gè)類(lèi)的時(shí)候是可以成功的璃吧。
2.而如果是系統(tǒng)控件如ImageVIew是沒(méi)有全稱(chēng)的楣导,所以我們需要加上前綴,就是包名畜挨,這個(gè)我們得看我們用到的幾個(gè)控件分別在哪幾個(gè)包里面然后放進(jìn)sClassPrefix數(shù)組里筒繁,最后分別遍歷去嘗試創(chuàng)建噩凹。
具體的創(chuàng)建代碼如下:
private View createViewOrFailQuietly(String name, String prefix, Context context,
AttributeSet attrs) {
try {
//通過(guò)系統(tǒng)的inflater創(chuàng)建視圖,讀取系統(tǒng)的屬性
return inflater.createView(name, prefix, attrs);
} catch (Exception e) {
return null;
}
}
這個(gè)地方的inflater就是我們自己自定義的LayoutInflater毡咏,當(dāng)然最后創(chuàng)建完視圖,我們需要將我們自定義的屬性和視圖View關(guān)聯(lián)起來(lái),以便于我們?cè)诨瑒?dòng)的時(shí)候可以取出來(lái)設(shè)置。
所以我們最后調(diào)用了:
//實(shí)例化完成
if (view != null) {
//獲取自定義屬性,通過(guò)標(biāo)簽關(guān)聯(lián)到視圖上
setViewTag(view,context,attrs);
fragment.getViews().add(view);
}
這里面的setViewTag就是取出自定義屬性娄涩,然后將它設(shè)置給View:
private void setViewTag(View view, Context context, AttributeSet attrs) {
//所有自定義的屬性
int[] attrIds = {
R.attr.a_in,
R.attr.a_out,
R.attr.x_in,
R.attr.x_out,
R.attr.y_in,
R.attr.y_out};
//獲取
TypedArray a = context.obtainStyledAttributes(attrs, attrIds);
if (a != null && a.length() > 0) {
//獲取自定義屬性的值
ParallaxViewTag tag = new ParallaxViewTag();
tag.alphaIn = a.getFloat(0, 0f);
tag.alphaOut = a.getFloat(1, 0f);
tag.xIn = a.getFloat(2, 0f);
tag.xOut = a.getFloat(3, 0f);
tag.yIn = a.getFloat(4, 0f);
tag.yOut = a.getFloat(5, 0f);
//index
view.setTag(R.id.parallax_view_tag,tag);
}
a.recycle();
}
到這里我們的自定義LayoutInflater已經(jīng)自定義完畢。其實(shí)這就是這篇文章的關(guān)鍵點(diǎn)张惹,為了講解代碼的完整性,這里還不算完,但是到這里我的目的已經(jīng)達(dá)到,先休息五分鐘唠帝。瀑晒。把介。
4.Fragment onCreateView
我們自定義完我們的LayoutInflater巢墅,那我們要使用呀,這篇文章的效果因?yàn)槲覀兪怯肰iewPager里面放置Fragment会前,所以我們的一頁(yè)一頁(yè)布局都是在Fragment里面反璃,所以我們?cè)贔ragment的onCreateView中創(chuàng)建視圖的時(shí)候用上它:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Bundle bundle = getArguments();
int layoutId = bundle.getInt("layoutId");
ParallaxLayoutInflater layoutInflater = new ParallaxLayoutInflater(inflater, getActivity(),this);
return layoutInflater.inflate(layoutId, null);
}
我們自定義的ParallaxLayoutInflater 派上用場(chǎng)了侧蘸,這個(gè)地方我們?cè)趧?chuàng)建視圖的時(shí)候就會(huì)走我們自己的LayoutInflater了逢艘,內(nèi)心是不是無(wú)比激動(dòng)。
5.ParallxContainer setUp
我們開(kāi)頭在基礎(chǔ)用法里面看到我們用的時(shí)候會(huì)調(diào)用這個(gè)方法崩瓤,那么我們來(lái)看這個(gè)方法做了啥:
public void setUp(int... childIds) {
fragments = new ArrayList<>();
for (int i = 0; i < childIds.length; i++) {
ParallaxFragment f = new ParallaxFragment();
Bundle args = new Bundle();
//頁(yè)面索引
args.putInt("index", i);
//Fragment中需要加載的布局文件id
args.putInt("layoutId", childIds[i]);
f.setArguments(args);
fragments.add(f);
}
//實(shí)例化適配器
SplashActivity activity = (SplashActivity)getContext();
adapter = new ParallaxAdapter(activity.getSupportFragmentManager(), fragments);
//實(shí)例化ViewPager
ViewPager vp = new ViewPager(getContext());
vp.setId(R.id.parallax_pager);
vp.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
//綁定
vp.setAdapter(adapter);
addView(vp,0);
vp.addOnPageChangeListener(this);
}
到現(xiàn)在大家應(yīng)該都看得懂這段代碼黔攒,就是創(chuàng)建出幾個(gè)Fragment,然后實(shí)例化適配器給ViewPager旅掂,最后設(shè)置了個(gè)ViewPager的監(jiān)聽(tīng)。這個(gè)監(jiān)聽(tīng)還蠻重要的栅哀,因?yàn)槲覀兘壎薞iew和自定義的屬性之后疫向,在這邊要取出來(lái)真正根據(jù)自定義的屬性來(lái)設(shè)置動(dòng)畫(huà)呀。
6.ParallxContainer onPageScrolled
我們的ParallxContainer 實(shí)現(xiàn)了OnPageChangeListener接口琢蛤,所以這幾個(gè)方法也在這個(gè)類(lèi)里面重寫(xiě)了儡率。第一個(gè)是滑動(dòng)時(shí)候要做的事情:
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
this.containerWidth = getWidth();
//在翻頁(yè)的過(guò)程中挂据,不斷根據(jù)視圖的標(biāo)簽中對(duì)應(yīng)的動(dòng)畫(huà)參數(shù),改變視圖的位置或者透明度
//獲取到進(jìn)入的頁(yè)面
ParallaxFragment inFragment = null;
try {
inFragment = fragments.get(position - 1);
} catch (Exception e) {}
//獲取到退出的頁(yè)面
ParallaxFragment outFragment = null;
try {
outFragment = fragments.get(position);
} catch (Exception e) {}
if (inFragment != null) {
//獲取Fragment上所有的視圖儿普,實(shí)現(xiàn)動(dòng)畫(huà)效果
List<View> inViews = inFragment.getViews();
if (inViews != null) {
for (View view : inViews) {
//獲取標(biāo)簽崎逃,從標(biāo)簽上獲取所有的動(dòng)畫(huà)參數(shù)
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
if (tag == null) {
continue;
}
//translationY改變view的偏移位置,translationY=100眉孩,代表view在其原始位置向下移動(dòng)100
//仔細(xì)觀察進(jìn)入的fragment中view從遠(yuǎn)處過(guò)來(lái)个绍,不斷向下移動(dòng),最終停在原始位置
ViewHelper.setTranslationY(view, (containerWidth - positionOffsetPixels) * tag.yIn);
ViewHelper.setTranslationX(view, (containerWidth - positionOffsetPixels) * tag.xIn);
}
}
}
if(outFragment != null){
List<View> outViews = outFragment.getViews();
if (outViews != null) {
for (View view : outViews) {
ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
if (tag == null) {
continue;
}
//仔細(xì)觀察退出的fragment中view從原始位置開(kāi)始向上移動(dòng)浪汪,translationY應(yīng)為負(fù)數(shù)
ViewHelper.setTranslationY(view, 0 - positionOffsetPixels * tag.yOut);
ViewHelper.setTranslationX(view, 0 - positionOffsetPixels * tag.xOut);
}
}
}
}
這段代碼注釋還是比較清楚的巴柿,我在這里簡(jiǎn)單說(shuō)一下邏輯,這個(gè)地方就是取出前一個(gè)fragment和后一個(gè)要進(jìn)入的fragment死遭,然后遍歷這個(gè)fragment里面的視圖控件广恢,根據(jù)剛才視圖控件綁定的屬性來(lái)設(shè)置該控件的動(dòng)畫(huà)。
7.ParallxContainer onPageSelected
這個(gè)方法很簡(jiǎn)單呀潭,就是看目前處于第幾個(gè)頁(yè)面來(lái)判斷中間走路的女生要不要顯示:
@Override
public void onPageSelected(int position) {
if (position == adapter.getCount() - 1) {
iv_man.setVisibility(INVISIBLE);
}else{
iv_man.setVisibility(VISIBLE);
}
}
8.ParallxContainer onPageScrollStateChanged
下面這個(gè)方法也很簡(jiǎn)單钉迷,就是判斷ViewPager處于什么狀態(tài),跟著這個(gè)女生要開(kāi)始走動(dòng)還是不走動(dòng)钠署,這個(gè)地方的動(dòng)畫(huà)效果是用幀動(dòng)畫(huà)來(lái)做的糠聪。
@Override
public void onPageScrollStateChanged(int state) {
AnimationDrawable animation = (AnimationDrawable) iv_man.getBackground();
switch (state) {
case ViewPager.SCROLL_STATE_DRAGGING:
animation.start();
break;
case ViewPager.SCROLL_STATE_IDLE:
animation.stop();
break;
default:
break;
}
}
到這里我們就說(shuō)完我們的代碼了,其實(shí)除了那個(gè)知識(shí)點(diǎn)其他都是基礎(chǔ)谐鼎,想必不會(huì)難倒大家舰蟆,如果哪里有說(shuō)的不清楚的,大家可以留言說(shuō)一下该面。Have a good journey夭苗!
總結(jié):在這里依舊要總結(jié)一下,這個(gè)地方主要用到的知識(shí)點(diǎn)其實(shí)跟support v7庫(kù)用到的是一模一樣隔缀,所以很多知識(shí)是關(guān)聯(lián)的题造,希望大家循序漸進(jìn),不驕不躁猾瘸,在Android的旅程中快樂(lè)界赔,一起成長(zhǎng)丢习。