Android自訂UI陷阱:LayoutInflater.from().inflate()一定不能工作在父類或虛類裡
問題背景:有一些UI具有共性,比如常見的app第一次運行時出現的各種指示框,告訴你往哪搓是調音量的,往哪點是調螢幕亮度的,當點擊這些VIew,則其自動消失。或者一動時間後,自動消失。另外一個問題是,不同的方向下載入出來的指示View內容是不一樣的。
為此需要將這些特點的View抽象出來,寫個父類或者說是基類,為啥一定要這樣搞,這樣寫好處很多。優點如下:
1、可以讓代碼變得更簡潔。每個子View裡的共同的方法都由父類來做,每個子View實現自己的邏輯就ok了。
2、因為這些View只工作一次,所以寫死在主UI的xml裡顯得不合時宜,動態添加是最好的。因為牽涉到旋轉方向問題,就必須要提前給出這些View的執行個體化變數名稱。如果互相之間是完全是獨立的,則需要定義View1 view1, View2 view2...很多個View,然後方向發生變化時挨個通知。如果有個BaseView, View1和View2...都是繼承自BaseView,則只需定義BaseView baseView,需要顯示時用BaseView執行個體化具體的是View1 還是View2.如: baseView = new View1(...).然後方向變化時判斷baseView是否為空白,然後把方向告訴它就ok了。
先來看上面提到的BaseView,這裡命名為BaseGuideView:
package org.yanzi.ui;import org.yanzi.util.OrientationUtil;import android.content.Context;import android.view.MotionEvent;import android.view.View;import android.widget.RelativeLayout;import android.widget.TextView;public abstract class BaseGuideView extends RelativeLayout implements Rotatable, View.OnClickListener {protected int mOrientation = 0;protected Context mContext;private GuideViewCallback mGuideViewCallback;public interface GuideViewCallback{public void onGuideViewClick();}public BaseGuideView(Context context, GuideViewCallback callback) {super(context);// TODO Auto-generated constructor stubmContext = context;mGuideViewCallback = callback;setOnClickListener(this);mOrientation = OrientationUtil.getOrientation();}@Overridepublic void setOrientation(int orientation, boolean animation) {// TODO Auto-generated method stubmOrientation = orientation;requestLayout();}protected abstract void initView();@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubreturn true; //super.onInterceptTouchEvent(ev)}@Overridepublic void onClick(View v) {// TODO Auto-generated method stubmGuideViewCallback.onGuideViewClick();}}
最重要一點是我再onInterceptTouchEvent裡把點擊事件給消費了,這樣布局裡的孩子就接收不到點擊了。然後寫了一個GuideViewCallback,當被點擊時,會觸發onGuideViewClick,這個介面的實現在另一個地方,如集中管理Ui的地方。將這個彈框再消失。另外,就是每次方向發生改變都會執行requestLayout,重新執行view的onMeasure和onLayout.
再定義個NanShiGuide.java繼承自上面的類:
package org.yanzi.ui;import com.example.test1.R;import android.content.Context;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.widget.TextView;public class NanShiGuide extends BaseGuideView {int LAYOUT_ID = R.layout.c_nanshi_guide;View guideNanLayout;TextView guideNanText;public NanShiGuide(Context context, GuideViewCallback callback) {super(context, callback);// TODO Auto-generated constructor stubinitView();}@Overrideprotected void initView() {// TODO Auto-generated method stubLog.i("YanZi", "NanShiGuide initView enter...");View v = LayoutInflater.from(mContext).inflate(LAYOUT_ID, this, true);guideNanLayout = v.findViewById(R.id.guide_nan_layout);guideNanText = (TextView) v.findViewById(R.id.guide_nan_text);Log.i("YanZi", "NanShiGuide initView exit...");}}
在這個子類裡就可以將資源載入進來了。對應的布局c_nanshi_guide.xml:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" ><FrameLayout android:id="@+id/guide_nan_layout" android:layout_width="200dip" android:layout_height="150dip" android:background="@drawable/nan1"> <TextView android:id="@+id/guide_nan_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:layout_gravity="bottom|center_horizontal" android:textColor="@android:color/white" android:textSize="20sp" android:text="南公懷瑾."/> </FrameLayout> </RelativeLayout>
在initView函數裡將xml載入進來並獲得各個控制項的執行個體,我所遇到的問題是,如果這個initView()寫在基類(也是個虛類)BaseGuideView的建構函式裡,是不能夠正常啟動並執行。雖然initView()函數執行了,但是會報錯:
07-06 15:17:58.258 I/YanZi ( 8375): NanShiGuide initView enter...07-06 15:17:58.258 W/ResourceType( 8375): No package identifier when getting value for resource number 0x0000000007-06 15:17:58.258 D/AndroidRuntime( 8375): Shutting down VM07-06 15:17:58.258 W/dalvikvm( 8375): threadid=1: thread exiting with uncaught exception (group=0x410899a8)
找不到package的指標。按理說從java的文法上是完全可以這麼用的,虛類調一個虛方法,虛方法由各個子類具體實現,但這裡報錯了。原因是因為:
View v = LayoutInflater.from(mContext).inflate(LAYOUT_ID, this, true);
這裡有個this指標的問題,當initVIew()讓虛類調用時,這個this指向誰?是虛類自己還是子類?正因此才掛了,另外這個inflate本身就有一定特殊性,是不能隨便亂用this的。我嘗試過把BaseGuideView裡的initView不寫成虛的,而是一個空的函數,依舊是報錯。所以遇到這種情況,載入布局一定由各個子View自行載入並初始化是最好的。
效果如下,左上方的圖片就是特意顯示的,同時將背景變暗: