关于github上一个可拓展和折叠的测试demo源码详解

一个屌丝安卓程序员正在努力向着大神方向靠近,为了能够自己去写像github上面那边能够自己动手做一些自己的动态效果,然后就在github上看别人写的代码,然后分析别人写的代码,it is just the beginning!!!

      今天分析的是一个github上一个可拓展和折叠的测试demo,接下来就是一步步分析代码的作用,有些英文是原作者自己写的,我就不动它了,也比较好懂。

/*
 * Copyright (C) 2011 The Android Open Source Project
 * Copyright 2014 Manabu Shimobe
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.expandabletvtest;


import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;




public class ExpandableTextView extends LinearLayout implements View.OnClickListener {


    private static final String TAG = ExpandableTextView.class.getSimpleName();


    /* The default number of lines */
    /**
     * 先说说下面变量申明定义是有规则和顺序的,而且按照这样子的规则和顺序我们也是能够见名知意。
     * 第一个是三个static final,这三个是常量,用final表示,而且后面两个常量名里有Default,所以这个三个常量是作为后面如果找不到对应的变量的默认值,这样做就不会出现空指针异常了。
     * 第二顺序是TextView ,ImageView,这两个变量是视图中所需要和看到的组件,所以应该将他们归为以为一类。
     * 第三顺序是组件进行动态演示时需要有flag来表示对应的状态。mRelayout和mCollapsed就是这样的作用
     * 第四顺序是对应布局或者组件动态变化要求的一些需要固定的属性大小。
     * 最后一个是动画效果时候需要要求的属性。
     * 
     * 
     * 接下来就考虑方法何时调用了,也就是什么时候会触发方法或者构造函数了。这样子我们就能按照这样子的顺序去一步步去,
     * 如果有时候不知道两个方法谁最先调用,用Log来看顺序。
     * 第一步,当然是构造函数,三个函数对号入座。
     * 第二步     onFinishInflate()
     * 第三步    onMeasure
     * 其他的作用或者作用比较简单,或者就是只有有活动时才粗发,所以就在下面慢慢讲

     * */
    private static final int MAX_COLLAPSED_LINES = 8;


    /* The default animation duration */
    private static final int DEFAULT_ANIM_DURATION = 300;


    /* The default alpha value when the animation starts */
    private static final float DEFAULT_ANIM_ALPHA_START = 0.7f;


    protected TextView mTv;


    protected ImageButton mButton; // Button to expand/collapse


    private boolean mRelayout;


    private boolean mCollapsed = true; // Show short version as default.


    private int mCollapsedHeight;


    private int mTextHeightWithMaxLines;


    private int mMaxCollapsedLines;


    private int mMarginBetweenTxtAndBottom;


    private Drawable mExpandDrawable;


    private Drawable mCollapseDrawable;


    private int mAnimationDuration;


    private float mAnimAlphaStart;


    private boolean mAnimating;
    /*
     * listener用来监听折叠转态转变是需要回调的接口
     * */
    /* Listener for callback */
    private OnExpandStateChangeListener mListener;
    /**
     * 用一个稀疏数组来保存一个当前的状态,用稀疏数组的目的是为了节省空间。
     */
    /* For saving collapsed status when used in ListView */
    private SparseBooleanArray mCollapsedStatus;
    private int mPosition;


    public ExpandableTextView(Context context) {
        this(context, null);
    }
    /**
     *下面这个方法是会调用自己定义的属性,并且初始化上面的一些变量 ,如果用户没有提供数组,则使用默认值
     */
    
    public ExpandableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }


    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public ExpandableTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }


    @Override
    public void setOrientation(int orientation){
        if(LinearLayout.HORIZONTAL == orientation){
            throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");
        }
        super.setOrientation(orientation);
    }
    /*
     * onClick()方法是整个自定义中最关键的一部分,首先,你可以看到在方法体里首先有个判断mButton是不是可见的,如果这里没有设置就会出现很大的出路,让我们想想判断Button
     * 可见不见的初衷,在这个自定义视图中,如果文本的高度小于用户自己定义的文本高度或者默认的文本高度,这个时候mButton就应该被隐藏 了,因为这个时候mButton就没有多大作用了,
     * 如果这个时候不隐藏 ,那么问题来了,接下的一些事情就按照没有隐藏的mButton的事件去做,去要求。讲完mButton之后,现在要判断当前的状态以及动态效果的状态。在ExpandCollapseAnimation()
     * 两个参数表示的初始高度和最终高度。然后动画中实现了监听了。

     * */
    @Override
    public void onClick(View view) {
        if (mButton.getVisibility() != View.VISIBLE) {
            return;
        }


        mCollapsed = !mCollapsed;
        mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);


        if (mCollapsedStatus != null) {
            mCollapsedStatus.put(mPosition, mCollapsed);
        }


        // mark that the animation is in progress
        mAnimating = true;


        Animation animation;
        if (mCollapsed) {
            animation = new ExpandCollapseAnimation(this, getHeight(), mCollapsedHeight);
        } else {
            animation = new ExpandCollapseAnimation(this, getHeight(), getHeight() +
                    mTextHeightWithMaxLines - mTv.getHeight());
        }


        animation.setFillAfter(true);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                //applyAlphaAnimation(mTv, mAnimAlphaStart);
            }
            @Override
            public void onAnimationEnd(Animation animation) {
                // clear animation here to avoid repeated applyTransformation() calls
                clearAnimation();
                // clear the animation flag
                mAnimating = false;


                // notify the listener
                if (mListener != null) {
                    mListener.onExpandStateChanged(mTv, !mCollapsed);
                }
            }
            @Override
            public void onAnimationRepeat(Animation animation) { }
        });
        //should be clearanimation before startAnimation
        clearAnimation();
        startAnimation(animation);
    }
    /*
     * 这里的作用就是当状态正在进行时,不可以将点击事件或者其他可能的事件传递给下面的界面或者活动。
     * */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // while an animation is in progress, intercept all the touch events to children to
        // prevent extra clicks during the animation
        return mAnimating;
    }


    @Override
    protected void onFinishInflate() {
    super.onFinishInflate();
        findViews();
    }
    /*
     * 这里的作用和onClick()作用相当,如果是第一次使用SetText,那么这个方法会被调用,首先看看第一个的判断,mRelayout为false说明不允许重新设置布局的属性,在setText()初始化时
     * mRelayout是true,所以在第一次使用时,这个mRelayout为真,那么就跳过判断体,执行接下来的操作,然后重新mRelayout为false,表示下次如果没有setText(),那么这个
     * 就直接进入判断体,然后就直接返回了。第二个判断是判断当前的自定义LinearLayout是不是可见的,如果不可见,那么执行下面的函数体就没有多大作用。
     *
*/
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


        // If no change, measure and return
        if (mRelayout==false || getVisibility() == View.GONE) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }
        
        mRelayout = false;


        // Setup with optimistic case
        // i.e. Everything fits. No button needed
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mButton.setVisibility(View.GONE);
       // mTv.setMaxLines(Integer.MAX_VALUE);
        Log.e("mButton is gone","...");
        // Measure
        


        // If the text fits in collapsed mode, we are done.
        //如果文本高度小于想要设置的高度,那么就直接返回,不需要执行相关的布局属性的赋值。
        if (mTv.getLineCount() <= mMaxCollapsedLines) {
            return;
        }
        
        // Saves the text height w/ max lines
        mTextHeightWithMaxLines = getRealTextViewHeight(mTv);


        // Doesn't fit in collapsed mode. Collapse text view as needed. Show
        // button.
        
        if (mCollapsed) {
        /**
        * 这个方法有个坑,因为这个方法是只有这次有用,下次的时候最大的行数还是文本能够包容现有文字的高度,不是自己想要设置的高度。
        * 所以为什么在这里敢这样设置,如果不是只有这次有用,那么文本的高度就一直那么保持不变了。
        */
            mTv.setMaxLines(mMaxCollapsedLines);
        }
        mButton.setVisibility(View.VISIBLE);


        // Re-measure with new setup
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e("mCollapsed_onMeasure="+mCollapsed,".....");/*
        Log.e("mCollapsedHeight_onMeasure="+mCollapsedHeight,".....");
        Log.e("getHeight_onMeasure="+getHeight(),".....");
        Log.e("getMeasureHeight()_onMeasure="+getMeasuredHeight(),".....");*/
        if (mCollapsed) {
            // Gets the margin between the TextView's bottom and the ViewGroup's bottom
        Log.e("getheight="+getHeight(),"onMeasure");
        Log.e("mTv.getHeight="+mTv.getHeight(),"onMeasure");
        /**
        * 通过观察,mTv.getheith()在Runnable是不起作用的,得到的值是只有0,那么这个Runnable的作用是干嘛的,他的作用是把Runnable要执行的东西推到消息队列里,
        * 等整个布局都设置好了之后,就能准确知道这些方法能够返回准确值了。这个方法容易被忽略。

        */
            mTv.post(new Runnable() {
                @Override
                public void run() {
                    mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight();
                    Log.e("getheight="+getHeight(),"onMeasure");
                Log.e("mTv.getHeight="+mTv.getHeight(),"onMeasure");
                }
            });
            // Saves the collapsed height of this ViewGroup
            mCollapsedHeight = getMeasuredHeight();
        }
    }


    public void setOnExpandStateChangeListener(@Nullable OnExpandStateChangeListener listener) {
        mListener = listener;
    }


    public void setText(@Nullable CharSequence text) {
        mRelayout = true;
        mTv.setText(text);
        setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
    }
    /*
     * 这里setText处理设置文本之外还设置了一些 变量的初始化工作。
     * */
    public void setText(@Nullable CharSequence text, @NonNull SparseBooleanArray collapsedStatus, int position) {
        mCollapsedStatus = collapsedStatus;
        mPosition = position;
        boolean isCollapsed = collapsedStatus.get(position, true);
        clearAnimation();
        mCollapsed = isCollapsed;
        mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);
        setText(text);
        getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
        requestLayout();
    }


    @Nullable
    public CharSequence getText() {
        if (mTv == null) {
            return "";
        }
        return mTv.getText();
    }


    private void init(AttributeSet attrs) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextView);
        mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandableTextView_maxCollapsedLines, MAX_COLLAPSED_LINES);
        mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextView_animDuration, DEFAULT_ANIM_DURATION);
        mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandableTextView_animAlphaStart, DEFAULT_ANIM_ALPHA_START);
        mExpandDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_expandDrawable);
        mCollapseDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_collapseDrawable);


        if (mExpandDrawable == null) {
            mExpandDrawable = getDrawable(getContext(), R.drawable.ic_expand_small_holo_light);
        }
        if (mCollapseDrawable == null) {
            mCollapseDrawable = getDrawable(getContext(), R.drawable.ic_collapse_small_holo_light);
        }


        typedArray.recycle();


        // enforces vertical orientation
        setOrientation(LinearLayout.VERTICAL);


        // default visibility is gone
        setVisibility(GONE);
    }


    private void findViews() {
        mTv = (TextView) findViewById(R.id.expandable_text);
        Log.e("getheight="+getHeight(),"onfinishLayout");
    Log.e("mTv.getHeight="+mTv.getHeight(),"");
        mTv.setOnClickListener(this);
        mButton = (ImageButton) findViewById(R.id.expand_collapse);
        mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);
        mButton.setOnClickListener(this);
    }


    private static boolean isPostHoneycomb() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }


    private static boolean isPostLolipop() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }


    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private static void applyAlphaAnimation(View view, float alpha) {
        if (isPostHoneycomb()) {
            view.setAlpha(alpha);
        } else {
            AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha);
            // make it instant
            alphaAnimation.setDuration(0);
            alphaAnimation.setFillAfter(true);
            view.clearAnimation();
            view.startAnimation(alphaAnimation);
        }
    }


    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
        Resources resources = context.getResources();
        if (isPostLolipop()) {
            return resources.getDrawable(resId);
        } else {
            return resources.getDrawable(resId);
        }
    }


    private static int getRealTextViewHeight(@NonNull TextView textView) {
        int textHeight = textView.getLayout().getLineTop(textView.getLineCount());
        int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();
        return textHeight + padding;
    }


    class ExpandCollapseAnimation extends Animation {
        private final View mTargetView;
        private final int mStartHeight;
        private final int mEndHeight;


        public ExpandCollapseAnimation(View view, int startHeight, int endHeight) {
            mTargetView = view;
            mStartHeight = startHeight;
            mEndHeight = endHeight;
            setDuration(mAnimationDuration);
        }


        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            
        /*Log.e("mCollapsed_applyTransformation="+mCollapsed,".....");
            Log.e("mCollapsedHeight_applyTransformation="+mCollapsedHeight,".....");
            Log.e("getHeight_applyTransformation="+getHeight(),".....");
            Log.e("getMeasureHeight()_applyTransformation="
            +getMeasuredHeight(),".....");*/
        final int newHeight = (int)((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight);
            mTv.setMaxHeight(newHeight - mMarginBetweenTxtAndBottom);
            if (Float.compare(mAnimAlphaStart, 1.0f) != 0) {
                applyAlphaAnimation(mTv, mAnimAlphaStart + interpolatedTime * (1.0f - mAnimAlphaStart));
            }
            mTargetView.getLayoutParams().height = newHeight;
            mTargetView.requestLayout();
        }


        @Override
        public void initialize( int width, int height, int parentWidth, int parentHeight ) {
            super.initialize(width, height, parentWidth, parentHeight);
        }


        @Override
        public boolean willChangeBounds( ) {
            return true;
        }
    };


    public interface OnExpandStateChangeListener {
        /**
         * Called when the expand/collapse animation has been finished
         *
         * @param textView - TextView being expanded/collapsed
         * @param isExpanded - true if the TextView has been expanded
         */
        void onExpandStateChanged(TextView textView, boolean isExpanded);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值