org.lib.ExpandableLinearLayout.java Source code

Java tutorial

Introduction

Here is the source code for org.lib.ExpandableLinearLayout.java

Source

/*
 * Copyright 2016. Arshad Ali <arshadalisoomro7@gmail.com.com>
 *
 * 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 org.lib;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;

import org.arshad.nimbleme.R;

import java.util.ArrayList;
import java.util.List;

/**
 * from https://github.com/cachapa/ExpandableLayout/blob/master/lib/src/main/java/net/cachapa/expandablelayout/ExpandableLinearLayout.java
 */
public class ExpandableLinearLayout extends LinearLayout {

    public static final String KEY_SUPER_STATE = "super_state";
    public static final String KEY_EXPANDED = "expanded";

    private static final int DEFAULT_DURATION = 300;

    private int wms;
    private int hms;

    private List<View> expandableViews;

    private int duration = DEFAULT_DURATION;
    private boolean expanded = false;

    private Interpolator interpolator = new FastOutSlowInInterpolator();
    private AnimatorSet animatorSet;

    private OnExpansionUpdateListener listener;

    public ExpandableLinearLayout(Context context) {
        super(context);
        init(null);
    }

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

    public ExpandableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public ExpandableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableLayout);
            duration = a.getInt(R.styleable.ExpandableLayout_el_duration, DEFAULT_DURATION);
            expanded = a.getBoolean(R.styleable.ExpandableLayout_el_expanded, false);
            a.recycle();
        }

        expandableViews = new ArrayList<>();

        // We only support vertical layouts for now
        setOrientation(VERTICAL);
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        Bundle bundle = new Bundle();

        bundle.putBoolean(KEY_EXPANDED, expanded);
        bundle.putParcelable(KEY_SUPER_STATE, superState);

        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        Bundle bundle = (Bundle) state;
        expanded = bundle.getBoolean(KEY_EXPANDED);
        Parcelable superState = bundle.getParcelable(KEY_SUPER_STATE);

        for (View expandableView : expandableViews) {
            expandableView.setVisibility(expanded ? VISIBLE : GONE);
        }

        super.onRestoreInstanceState(superState);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        LayoutParams lp = (LayoutParams) params;
        if (lp.expandable) {
            expandableViews.add(child);
            child.setVisibility(expanded ? VISIBLE : GONE);
        }

        super.addView(child, index, params);
    }

    @Override
    public void removeView(View child) {
        LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (lp.expandable) {
            expandableViews.remove(child);
        }

        super.removeView(child);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        wms = widthMeasureSpec;
        hms = heightMeasureSpec;
    }

    @Override
    public LinearLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        if (animatorSet != null) {
            animatorSet.cancel();
        }
        super.onConfigurationChanged(newConfig);
    }

    public boolean isExpanded() {
        return expanded;
    }

    public void toggle() {
        toggle(true);
    }

    public void toggle(boolean animate) {
        if (expanded) {
            collapse(animate);
        } else {
            expand(animate);
        }
    }

    public void expand() {
        expand(true);
    }

    @SuppressLint("WrongCall")
    public void expand(boolean animate) {
        if (expanded) {
            return;
        }

        if (animatorSet != null) {
            animatorSet.cancel();
            animatorSet = null;
        }

        expanded = true;

        for (View expandableView : expandableViews) {
            LayoutParams lp = (LayoutParams) expandableView.getLayoutParams();

            // Calculate view's original height
            expandableView.setVisibility(View.VISIBLE);
            lp.weight = lp.originalWeight;
            lp.height = lp.originalHeight;
            super.onMeasure(wms, hms);
        }

        for (View expandableView : expandableViews) {
            if (animate) {
                animateHeight(expandableView, expandableView.getMeasuredHeight());
            } else {
                setHeight(expandableView, expandableView.getMeasuredHeight());
            }
        }

        if (animatorSet != null && animate) {
            animatorSet.start();
        }
    }

    public void collapse() {
        collapse(true);
    }

    public void collapse(boolean animate) {
        if (!expanded) {
            return;
        }

        if (animatorSet != null) {
            animatorSet.cancel();
            animatorSet = null;
        }

        expanded = false;

        for (View expandableView : expandableViews) {
            if (animate) {
                animateHeight(expandableView, 0);
            } else {
                setHeight(expandableView, 0);
            }
        }

        if (animatorSet != null && animate) {
            animatorSet.start();
        }
    }

    public void setOnExpansionUpdateListener(OnExpansionUpdateListener listener) {
        this.listener = listener;
    }

    private void animateHeight(final View view, final int targetHeight) {
        if (animatorSet == null) {
            animatorSet = new AnimatorSet();
            animatorSet.setInterpolator(interpolator);
            animatorSet.setDuration(duration);
        }

        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
        lp.weight = 0;
        int height = view.getHeight();

        ValueAnimator animator = ValueAnimator.ofInt(height, targetHeight);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                view.getLayoutParams().height = (Integer) valueAnimator.getAnimatedValue();
                view.requestLayout();

                if (listener != null) {
                    float fraction = targetHeight == 0 ? 1 - valueAnimator.getAnimatedFraction()
                            : valueAnimator.getAnimatedFraction();
                    listener.onExpansionUpdate(fraction);
                }
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                view.setVisibility(View.VISIBLE);
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                if (targetHeight == 0) {
                    view.setVisibility(GONE);
                } else {
                    lp.height = lp.originalHeight;
                    lp.weight = lp.originalWeight;
                }
            }

            @Override
            public void onAnimationCancel(Animator animator) {
            }

            @Override
            public void onAnimationRepeat(Animator animator) {
            }
        });

        animatorSet.playTogether(animator);
    }

    private void setHeight(View view, int targetHeight) {
        LayoutParams lp = (LayoutParams) view.getLayoutParams();

        if (targetHeight == 0) {
            view.setVisibility(GONE);
        } else {
            lp.height = lp.originalHeight;
            lp.weight = lp.originalWeight;

            view.requestLayout();
        }

        if (listener != null) {
            listener.onExpansionUpdate(targetHeight == 0 ? 0f : 1f);
        }
    }

    public static class LayoutParams extends LinearLayout.LayoutParams {
        private final boolean expandable;
        private final int originalHeight;
        private final float originalWeight;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ExpandableLayout);
            expandable = a.getBoolean(R.styleable.ExpandableLayout_layout_expandable, false);
            originalHeight = this.height;
            originalWeight = this.weight;
            a.recycle();
        }
    }

    public interface OnExpansionUpdateListener {
        void onExpansionUpdate(float expansionFraction);
    }
}