com.llf.nestlayout.library.NestLayout.java Source code

Java tutorial

Introduction

Here is the source code for com.llf.nestlayout.library.NestLayout.java

Source

/*
 *
 *  * Copyright 2015 llfer2006@gmail.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 com.llf.nestlayout.library;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * Created by llf on 2015/7/8.
 */
public class NestLayout extends ViewGroup implements NestedScrollingParent {

    public interface OnSectionChangedListener {
        void onSectionChanged(CharSequence old, CharSequence current);
    }

    private Scroller mScroller;
    /**
     * indicate which is current nested view,may be not this ViewGroup direct child
     */
    private View mNestedView;
    /**
     * Section Change Lisnter
     */
    private OnSectionChangedListener mSectionChangeListener;
    private int[] mTmpCoord;

    public NestLayout(Context context) {
        this(context, null);
    }

    public NestLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NestLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs);
        mScroller = new Scroller(context);
        mTmpCoord = new int[2];
    }

    public void setSectionChangeListener(OnSectionChangedListener listener) {
        mSectionChangeListener = listener;
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        if (nestedScrollAxes != ViewCompat.SCROLL_AXIS_VERTICAL) {
            return false;
        }
        return true;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {

    }

    @Override
    public void onStopNestedScroll(View target) {
        target = mNestedView;
        mNestedView = null;
        if (target == null)
            return;
        int pY = getScrollY() + getPaddingTop();
        View child = getChildFromTarget(target);
        //found section view from nested view;
        View section = findSectionView(indexOfChild(child));
        int dy = 0;
        //whether the section view needs scroll to really position
        if (pY == section.getTop()) {
            return;
        }
        getLocationOnScreen(mTmpCoord);
        int parentOffset = mTmpCoord[1];
        if (section.getTop() < pY) {
            View nextSection = findNextSectionView(indexOfChild(section));
            if (nextSection != null && section != nextSection) {
                nextSection.getLocationOnScreen(mTmpCoord);
                mTmpCoord[1] -= parentOffset;
                if (mTmpCoord[1] < getHeight() * 4 / 5) {
                    performSectionChange(section, nextSection);
                    dy = mTmpCoord[1];
                } else {
                    dy = mTmpCoord[1] - getHeight();
                }
            }
        } else {
            View preSection = findSectionView(indexOfChild(section) - 1);
            if (preSection != null && section != preSection) {
                preSection.getLocationOnScreen(mTmpCoord);
                mTmpCoord[1] -= parentOffset;
                if (mTmpCoord[1] > getHeight() / 5) {
                    performSectionChange(section, preSection);
                    dy = mTmpCoord[1] - getHeight();
                } else {
                    dy = mTmpCoord[1];
                }
            }
        }
        if (dy != 0) {
            mScroller.startScroll(0, pY, 0, dy);
            invalidate();
        }
    }

    protected void performSectionChange(View section, View next) {
        if (mSectionChangeListener != null) {
            CharSequence old = ((LayoutParams) section.getLayoutParams()).mSectionTag;
            CharSequence current = ((LayoutParams) next.getLayoutParams()).mSectionTag;
            mSectionChangeListener.onSectionChanged(old, current);
        }
    }

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        mNestedView = target;
        int pY = getScrollY() + getPaddingTop();
        View child = getChildFromTarget(target);
        View section = findSectionView(indexOfChild(child));
        if (section.getTop() == pY)
            return;
        if ((section.getTop() < pY && dy < 0) || (section.getTop() > pY && dy > 0)) {
            scrollByIfNeed(dy);
            consumed[1] = getScrollY() - pY;
        }
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        mNestedView = target;
        scrollByIfNeed(dyUnconsumed);
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return !mScroller.isFinished();
    }

    @Override
    public int getNestedScrollAxes() {
        return ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(-1, -2);
    }

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

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

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = MotionEventCompat.getActionMasked(ev);
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            onStopNestedScroll(null);
            //block the child view fling
            if (!mScroller.isFinished())
                ev.setAction(MotionEvent.ACTION_CANCEL);
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(0, mScroller.getCurrY());
            invalidate();
        } else {
            super.computeScroll();
        }
    }

    void scrollByIfNeed(int dy) {
        int scrollY = getScrollY();
        if (scrollY <= 0 && dy < 0)
            return;
        int childCount = getChildCount();
        if (childCount == 0)
            return;
        View lastChild = getChildAt(childCount - 1);
        if (scrollY + getHeight() >= lastChild.getTop() + lastChild.getMeasuredHeight() && dy > 0)
            return;
        int delta;
        if (dy > 0) {
            delta = Math.min(Math.max(lastChild.getTop() + lastChild.getMeasuredHeight() - scrollY, 0), dy);
        } else {
            delta = Math.max(dy, -scrollY);
        }
        scrollBy(0, delta);
    }

    View getChildFromTarget(View t) {
        while (t.getParent() != null && t.getParent() != this)
            t = (View) t.getParent();
        return t;
    }

    View findSectionView(int end) {
        int childCount = getChildCount();
        end = end < 0 ? childCount - 1 : Math.min(end, childCount - 1);
        View child;
        for (int i = end; i >= 0; i--) {
            child = getChildAt(i);
            if (child.getVisibility() == GONE)
                continue;
            if (isSectionView(child))
                return child;
        }
        return null;
    }

    View findNextSectionView(int start) {
        int childCount = getChildCount();
        View child;
        for (int i = start + 1; i < childCount; i++) {
            child = getChildAt(i);
            if (child.getVisibility() == GONE)
                continue;
            if (isSectionView(child))
                return child;
        }
        return null;
    }

    boolean isSectionView(View v) {
        LayoutParams lp = (LayoutParams) v.getLayoutParams();
        if (lp.isSection())
            return true;
        return indexOfChild(v) == 0;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int padLeft = getPaddingLeft();
        int padTop = getPaddingTop();
        int childCount = getChildCount();
        View child;
        LayoutParams lp;
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            lp = (LayoutParams) child.getLayoutParams();
            padTop += lp.topMargin;
            child.layout(padLeft + lp.leftMargin, padTop, padLeft + lp.leftMargin + child.getMeasuredWidth(),
                    padTop + child.getMeasuredHeight());
            padTop += child.getMeasuredHeight() + lp.bottomMargin;
        }
    }

    public static class LayoutParams extends MarginLayoutParams {
        private boolean mSection;
        private CharSequence mSectionTag;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.NestLayout);
            mSection = a.getBoolean(R.styleable.NestLayout_section, false);
            mSectionTag = a.getText(R.styleable.NestLayout_sectionTag);
            a.recycle();
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(LayoutParams p) {
            super(p);
            mSection = p.mSection;
            mSectionTag = p.mSectionTag;
        }

        public boolean isSection() {
            return mSection;
        }

        public CharSequence getSectionTag() {
            return mSectionTag;
        }

        public void setSection(boolean enable) {
            mSection = enable;
        }

        public void setSection(boolean enable, String tag) {
            mSection = enable;
            mSectionTag = tag;
        }
    }
}