Android UIカスタムビュー--任意のシステムコントロールにカスタム属性を追加

30651 ワード

タイトルを見ると、システムコントロールにカスタムプロパティを追加し、解析できると思います.これはちょっとあり得ないようです.よく考えられるのは、Viewをカスタマイズしてシステムコントロールを継承し、自分が書いたattrを解析することで、カスタム属性を使用することができますが、この場合はシステムコントロールではなく、カスタムコントロールです.私たちが実現したい効果は次のとおりです.

システムコントロールImageViewには、カスタムプロパティx_があります.inとx_outは解析的に使用できます(ここではImageViewを例に、他のどのシステムコントロールでも使用できます).
システムのソースコードから文章を作ることができます.Androidのコントロールがどのようにロードされているかを見てみましょう.すべてのxmlのコントロールのロードには、最終的にLayoutInflaterが必要です.処理の最終的な方法は、次のとおりです.
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException(" can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    //********************************************************************
                    //          else   ===               
                    //********************************************************************
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

最後のelseブランチはcreateView FromTagによって対応するViewを作成し、attrと親コンテナrootのgenerateLayoutParamsによってサブコントロールに対応するレイアウトプロパティを追加し、rootのaddViewメソッドを呼び出して、レンダリングされたViewを構築済みのViewツリーに追加します.上記のシステムコントロールにカスタムプロパティを追加すると、generateLayoutParamsとaddViewメソッドを書き換えることで、上記の効果を達成できます.たとえば、次のような効果を実現します.
任意のコントロールがscrollViewのスライドに従って、指定したアニメーション(平行移動、スケール、透明度)に従って変化する効果を見ました.私たちは上の提示に基づいて、上のソースコード分析のattrとgenerateLayoutParams,addViewをどのように利用するかを考えています.
1.まず、システムコントロールTextView、ImageView、またはレイアウトコンテナ、カスタムコントロールのいずれかで、設定した場合、対応するパンアニメーション、スケールアニメーション、または透明度アニメーションを行うことができます.そのため、柔軟な構成が必要です.追加されたすべてのコントロールのカスタムコントロールの実装形式を排除します(柔軟で構成可能なため、各コントロールのカスタマイズは煩雑で、面倒で、不可能です).


    
        
        
        
        
        
        
    

    
        
        
        
        
    

これらのカスタムアトリビュートを設定するように、コントロールの上に上のアトリビュートの値を設定するだけで、対応するアニメーションを実現できます.次のようなコントロールがあります.
 

上のコントロールでは、透明度アニメーションとY方向のスケールアニメーションを実行できます.
ここで解決すべき課題は、システムコントロールがカスタム属性を認識できないことです(前述の例と同様).
システムsupportのコントロールCardViewの効果を参考にして、もともとTextViewに属していない、ImageView自身の効果を包装容器CardViewの上に置いて実現することができます.

        
        
    

このような実装の描画を参考にして、私たちのViewの上で、XMLを解析する時、コードの中で(javaコードの中で、黙って追加して、ユーザーが自分でXmlファイルの中で追加する必要はありません)私たちのカスタムコンテナクラスのコントロールを追加して、それから私たちが自分で設定したカスタム属性を解析することができます.これにより、ImageView上のカスタム属性の数値を、外に包まれたコンテナコントロールに作用させることができます.これにより、コンテナコントロールにアニメーションを作用させ、中のシステムコントロールも対応するアニメーションを実現することができます.(注意javaコードではカスタムコンテナの小包を実現し、xmlでは実現せず、できるだけ使用者に自分の小包コンテナを書かせないようにする)


        
        
    

ここでは、上記のソースコード、干渉システムコントロールのロードinflateメソッドを使用する必要があります.Javaコードでは、カスタムMyFrameLayoutをシステムコントロールにカプセル化します.XMLでは構成を必要とせず,ユーザが感知しないように実現する.
具体的なコード実装:


    

        

        

        

         

        ......

    



私たちがカスタマイズしたMyLinearLayoutでは、コントロールImageView、TextView、または他のカスタムViewのカスタムプロパティを解読します.ImageView、TextViewの上にカスタムコンテナコントロールMyFrameLayoutを包み、MyFrameLayoutで平行移動、透明度、スケールするアニメーションが必要です.まずMyFrameLyoutの実現を見てみましょう.
package com.widget.discrollvedemo;

import android.animation.ArgbEvaluator;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;

public class MyFrameLayout extends FrameLayout implements DiscrollInterface{

    public MyFrameLayout(Context context,  AttributeSet attrs) {
        super(context, attrs);
    }

    //          
    /**
     * 
         
         
         
         
     
     0000000001
     0000000010
     0000000100
     0000001000
     top|left
     0000000001 top
     0000000100 left     |
     0000000101
           &    
     */
    private static final int TRANSLATION_FROM_TOP = 0x01;
    private static final int TRANSLATION_FROM_BOTTOM = 0x02;
    private static final int TRANSLATION_FROM_LEFT = 0x04;
    private static final int TRANSLATION_FROM_RIGHT = 0x08;

    //     
    private static ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();
    /**
     *              
     */
    private int mDiscrollveFromBgColor;//         
    private int mDiscrollveToBgColor;//         
    private boolean mDiscrollveAlpha;//         
    private int mDisCrollveTranslation;//   
    private boolean mDiscrollveScaleX;//    x     
    private boolean mDiscrollveScaleY;//    y     
    private int mHeight;// view   
    private int mWidth;//  

    public void setmDiscrollveFromBgColor(int mDiscrollveFromBgColor) {
        this.mDiscrollveFromBgColor = mDiscrollveFromBgColor;

    }

    public void setmDiscrollveToBgColor(int mDiscrollveToBgColor) {
        this.mDiscrollveToBgColor = mDiscrollveToBgColor;
    }

    public void setmDiscrollveAlpha(boolean mDiscrollveAlpha) {
        this.mDiscrollveAlpha = mDiscrollveAlpha;
    }

    public void setmDisCrollveTranslation(int mDisCrollveTranslation) {
        this.mDisCrollveTranslation = mDisCrollveTranslation;
    }

    public void setmDiscrollveScaleX(boolean mDiscrollveScaleX) {
        this.mDiscrollveScaleX = mDiscrollveScaleX;
    }

    public void setmDiscrollveScaleY(boolean mDiscrollveScaleY) {
        this.mDiscrollveScaleY = mDiscrollveScaleY;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // TODO Auto-generated method stub
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
//		onResetDiscroll();
    }

    @Override
    public void onDiscroll(float ratio) {
        //          ,    
        //ratio:0~1
        if(mDiscrollveAlpha){
            setAlpha(ratio);
        }
        if(mDiscrollveScaleX){
            setScaleX(ratio);
        }
        if(mDiscrollveScaleY){
            setScaleY(ratio);
        }
        //  ---int : left,right,top,bottom,   left|bottom
        if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){//   fromBottom
            setTranslationY(mHeight*(1-ratio));//height-->0 (0       )
        }
        if(isTranslationFrom(TRANSLATION_FROM_TOP)){//       
            setTranslationY(-mHeight*(1-ratio));//-height--->0
        }
        if(isTranslationFrom(TRANSLATION_FROM_LEFT)){
            setTranslationX(-mWidth*(1-ratio));//mWidth--->0(0            )
        }
        if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){
            setTranslationX(mWidth*(1-ratio));//-mWidth--->0(0            )
        }
        //            
        if(mDiscrollveFromBgColor!=-1&&mDiscrollveToBgColor!=-1){
            setBackgroundColor((int) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
        }


    }

    private boolean isTranslationFrom(int translationMask){
        if(mDisCrollveTranslation ==-1){
                return false;
        }
        //fromLeft|fromeBottom & fromBottom = fromBottom
        return (mDisCrollveTranslation & translationMask) == translationMask;
    }

    //    
    @Override
    public void onResetDiscroll() {
        int ratio = 0;
        //ratio:0~1
        if(mDiscrollveAlpha){
            setAlpha(ratio);
        }
        if(mDiscrollveScaleX){
            setScaleX(ratio);
        }
        if(mDiscrollveScaleY){
            setScaleY(ratio);
        }
        //  ---int : left,right,top,bottom,   left|bottom
        if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){//   fromBottom
            setTranslationY(mHeight*(1-ratio));//height-->0 (0       )
        }
        if(isTranslationFrom(TRANSLATION_FROM_TOP)){//       
            setTranslationY(-mHeight*(1-ratio));//-height--->0
        }
        if(isTranslationFrom(TRANSLATION_FROM_LEFT)){
            setTranslationX(-mWidth*(1-ratio));//mWidth--->0(0            )
        }
        if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){
            setTranslationX(mWidth*(1-ratio));//-mWidth--->0(0            )
        }
    }
}

MyFrameLayoutはパッケージコンテナで、解析が必要なカスタム属性が格納されていますが、解析はMyFrameLayoutではなく、ViewのsetXXXメソッドで、Viewに関するアニメーションを実現します.MyFrameLayoutにカスタム属性を格納すると、MyFrameLayoutの親コンテナであるMyLinearLayoutは関連する属性を解析し、MyFrameLayoutに格納する必要があります.
package com.widget.discrollvedemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context,  AttributeSet attrs) {
        super(context, attrs);
        setOrientation(VERTICAL);
    }

    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        //    ---childview        --->MyFrameLayout
        return new MyLayoutParams(getContext(), attrs);
    }

    //         ,  addView                   
    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        // child view        ----    
        MyLayoutParams p = (MyLayoutParams) params;

        if(!isDiscrollvable(p)){//             ,       MyFrameLayout
            super.addView(child, index, params);
        }else {
            MyFrameLayout mf = new MyFrameLayout(getContext(), null);
            mf.setmDiscrollveAlpha(p.mDiscrollveAlpha);
            mf.setmDiscrollveFromBgColor(p.mDiscrollveFromBgColor);
            mf.setmDiscrollveToBgColor(p.mDiscrollveToBgColor);
            mf.setmDiscrollveScaleX(p.mDiscrollveScaleX);
            mf.setmDiscrollveScaleY(p.mDiscrollveScaleY);
            mf.setmDisCrollveTranslation(p.mDisCrollveTranslation);
            mf.addView(child);
            super.addView(mf, index, params);
        }
    }

    private boolean isDiscrollvable(MyLayoutParams p) {
        return p.mDiscrollveAlpha||
                p.mDiscrollveScaleX||
                p.mDiscrollveScaleY||
                p.mDisCrollveTranslation!=-1||
                (p.mDiscrollveFromBgColor!=-1&&
                        p.mDiscrollveToBgColor!=-1);
    }

    private class MyLayoutParams extends LinearLayout.LayoutParams {
        public int mDiscrollveFromBgColor;//         
        public int mDiscrollveToBgColor;//         
        public boolean mDiscrollveAlpha;//         
        public int mDisCrollveTranslation;//   
        public boolean mDiscrollveScaleX;//    x     
        public boolean mDiscrollveScaleY;//    y     

        public MyLayoutParams(Context c, AttributeSet attrs) {
            super(c,attrs);
            //       
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
            mDiscrollveAlpha =  a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha,false);
            mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);
            mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);
            mDisCrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);
            mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);
            mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);
            a.recycle();

        }
    }
}

 
package com.widget.discrollvedemo;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;

public class MyScrollView extends ScrollView {
    MyLinearLayout mContent;
    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContent = (MyLinearLayout) getChildAt(0);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        int scrollveiwHeight = getHeight();
        //      --->childView        /childView.getHeight() =          ratio
        //          ,     ratio   !
        for (int i=0;i

以上の3つのカスタムコントロールMyFrameLayout javaコードに注入されたカスタムコントロールにより、カスタム属性を格納し、アニメーションの実行と返信を制御する操作により、XMLにカスタムViewをカプセル化して関連するカスタム属性を解析する必要がなくなります.MyLinearLayout解析システムのコントロールの上のカスタム属性はgenerateLayoutParamsとaddViewの上で文章を作って、MyScrollViewの中でonScrollChanged方法を書き直して、それからMyLinearLayoutの中で解析したカスタム属性を通じて、生成したMyFrameLayoutはそれから境界条件によって相応のアニメーションを作ります
LayoutInflaterの実装をカスタマイズ
AndroidのコントロールはLayoutInflaterのinflateメソッドでインタフェースにレンダリングされていることはよく知られています.では、LayoutInflaterをカスタマイズしてシステムコントロールのロードに介入し、システムコントロールに設定されたカスタム属性を解析し、システムコントロール自体の上に保存し(ここでview.setTagが解析するシステムコントロールのカスタム属性を呼び出し、自分にバインドする)、必要に応じて取り出し、対応する操作を行うことができます.
public class MyLayoutInflater extends LayoutInflater {

    public MyLayoutInflater(Context context){
        super(context);
        setFactory2(new Factory());
    }

    @Override
    public LayoutInflater cloneInContext(Context newContext) {
        return new MyLayoutInflater(newContext);
    }

    public static class Factory implements Factory2{
        private final String[] sClassPrefix = {
                "android.widget.",
                "android.view."
        };
        @Override
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            View view = null;
            if(name.contains(".")){
                view = createMyView(name,context,attrs);
            }else{
                for (String prefix : sClassPrefix) {
                    view = createMyView(prefix + name, context, attrs);
                    if (view != null) {
                        break;
                    }
                }
            }
            //  
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);
            if (a != null && a.length() > 0) {
                //         
                LayoutTag tag = new LayoutTag();
                tag.discrollve_alpha =  a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha,false);
                tag.discrollve_scaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);
                tag.discrollve_scaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);
                tag.discrollve_translation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);
                tag.discrollve_fromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);
                tag.discrollve_toBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);
                //index
                view.setTag(tag);
            }
            a.recycle();
            return view;
        }

        private View createMyView(String name, Context context, AttributeSet attrs){
            try {
                Class clazz = Class.forName(name);
                Constructor constructor = clazz.getConstructor(Context.class, AttributeSet.class);
                return  constructor.newInstance(context, attrs);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;

        }

        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            return null;
        }
    }

}


ここでは、システム属性をViewのtagに格納するため、対応する属性を取得するには、対応するViewを取得する必要があります.そのため、アニメーションはカスタムのViewにカプセル化できません.カスタムのScrollViewで対応するViewを取得し、対応するアニメーションの操作を行う必要があります.
package com.widget.discrollvedemo;

import android.animation.ArgbEvaluator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;

public class MyScrollView2 extends ScrollView {
    LinearLayout linearLayout;
    public MyScrollView2(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        linearLayout = (LinearLayout)getChildAt(0);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        int scrollveiwHeight = getHeight();
        //      --->childView        /childView.getHeight() =          ratio
        //          ,     ratio   !
        for (int i=0;i0 (0       )
        }
        if(isTranslationFrom(tag,TRANSLATION_FROM_TOP)){//       
            view.setTranslationY(-mHeight*(1-ratio));//-height--->0
        }
        if(isTranslationFrom(tag,TRANSLATION_FROM_LEFT)){
            view.setTranslationX(-mWidth*(1-ratio));//mWidth--->0(0            )
        }
        if(isTranslationFrom(tag,TRANSLATION_FROM_RIGHT)){
            view.setTranslationX(mWidth*(1-ratio));//-mWidth--->0(0            )
        }
        //            
        if(tag.discrollve_fromBgColor!=-1&&tag.discrollve_toBgColor!=-1){
            ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();
            view.setBackgroundColor((int) sArgbEvaluator.evaluate(ratio, tag.discrollve_fromBgColor, tag.discrollve_toBgColor));
        }


    }

    private boolean isTranslationFrom(LayoutTag tag,int translationMask){
        if(tag.discrollve_translation ==-1){
            return false;
        }
        //fromLeft|fromeBottom & fromBottom = fromBottom
        return (tag.discrollve_translation & translationMask) == translationMask;
    }

    public void onResetDiscroll(View view,LayoutTag tag,int mHeight,int mWidth) {
        int ratio = 0;
        //ratio:0~1
        if(tag.discrollve_alpha){
            view.setAlpha(ratio);
        }
        if(tag.discrollve_scaleX){
            view.setScaleX(ratio);
        }
        if(tag.discrollve_scaleY){
            view.setScaleY(ratio);
        }
        //  ---int : left,right,top,bottom,   left|bottom
        if(isTranslationFrom(tag,TRANSLATION_FROM_BOTTOM)){//   fromBottom
            view.setTranslationY(mHeight*(1-ratio));//height-->0 (0       )
        }
        if(isTranslationFrom(tag,TRANSLATION_FROM_TOP)){//       
            view.setTranslationY(-mHeight*(1-ratio));//-height--->0
        }
        if(isTranslationFrom(tag,TRANSLATION_FROM_LEFT)){
            view.setTranslationX(-mWidth*(1-ratio));//mWidth--->0(0            )
        }
        if(isTranslationFrom(tag,TRANSLATION_FROM_RIGHT)){
            view.setTranslationX(mWidth*(1-ratio));//-mWidth--->0(0            )
        }
    }
}

LinearLayoutとFrameLayoutをカスタマイズする必要はありません.xmlではscrollViewをカスタマイズするだけです.activty_my.xmlファイルは次のとおりです.


    

        

        

      

        

        

        

        

    


Activityでは、カスタムLayoutInflatorを使用してレンダリングに対応するコントロールを解析する必要があります.
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View view = new MyLayoutInflater(this).inflate(R.layout.activity_my,null);
        setContentView(view);
    }

これで,システム空間上のカスタム属性を実現する2つの方法の解析が実現された.普段の仕事の勉強では、ソースコードからインスピレーションを探して、多くの問題の突破口を見つけることができます.
Demoトランスポートゲート