net.soulwolf.widget.parallaxrefresh.ParallaxScrollLayout.java Source code

Java tutorial

Introduction

Here is the source code for net.soulwolf.widget.parallaxrefresh.ParallaxScrollLayout.java

Source

/**
 * <pre>
 * Copyright 2015 Soulwolf Ching
 * Copyright 2015 The Android Open Source Project for ParallaxScrollView
 *
 * 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.
 * </pre>
 */
package net.soulwolf.widget.parallaxrefresh;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
import android.widget.FrameLayout;

import net.soulwolf.widget.parallaxrefresh.event.OnRefreshListener;
import net.soulwolf.widget.parallaxrefresh.event.ParallaxScrollCallback;

/**
 * author: Soulwolf Created on 2015/9/4 12:53.
 * email : Ching.Soulwolf@gmail.com
 */
public class ParallaxScrollLayout extends FrameLayout implements ParallaxScrollCallback {

    static DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(10);
    static final Interpolator ROLLBACK_INTERPOLATOR = new DecelerateInterpolator();
    static final long ROLLBACK_DURATION = 300;
    static final long AUTO_REFRESH_DURATION = 400;

    private IParallaxHolder mParallaxHolder;
    private ParallaxMode mParallaxMode = ParallaxMode.PARALLAX_MODE_SCROLL;
    private LayoutInflater mLayoutInflater;
    private View mParallaxTarget;
    private View mPlaceholderView;
    private OnRefreshListener mOnRefreshListener;
    private float mRefreshRatio = .6f;

    private float mInitializeMotion;
    private float mDownMotion;
    private int mTouchSlop;

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

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

    public ParallaxScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        mLayoutInflater = LayoutInflater.from(context);
        applyAttributeSet(context, attrs, defStyleAttr);
    }

    private void applyAttributeSet(Context context, AttributeSet attrs, int defStyleAttr) {
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ParallaxScrollLayout, defStyleAttr, 0);
            mParallaxMode = ParallaxMode.ensure(a.getInt(R.styleable.ParallaxScrollLayout_psvParallaxMode, 0));
            a.recycle();
        }
    }

    /** Set up the head the View scroll parallax processor */
    public void setParallaxHolder(@NonNull IParallaxHolder holder) {
        if (mParallaxHolder != null) {
            mParallaxHolder.onDestroy();
            mParallaxHolder = null;
            mPlaceholderView = null;
        }
        mParallaxHolder = holder;
        mParallaxHolder.onCreate(getContext());
        mParallaxHolder.setParallaxMode(mParallaxMode);
        View view = mParallaxHolder.onCreateView(mLayoutInflater, this);
        // Attach to root view!
        addView(view, 0);
        mParallaxHolder.onViewCreated(view);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View view = getChildAt(i);
            if (view instanceof ParallaxScrollObserver) {
                mParallaxTarget = view;
                break;
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (tryParallaxHolder()) {
            View contentView = mParallaxHolder.getContentView();
            int width = contentView.getMeasuredWidth();
            int height = contentView.getMeasuredHeight();
            mParallaxHolder.onMeasured(width, height);
            ensurePlaceholderView();
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (tryParallaxHolder()) {
            View contentView = mParallaxHolder.getContentView();
            MarginLayoutParams params = (MarginLayoutParams) contentView.getLayoutParams();
            int l = getPaddingLeft() + params.leftMargin;
            int t = getPaddingTop() + params.topMargin - mParallaxHolder.getParallaxTop();
            contentView.layout(l, t, l + contentView.getMeasuredWidth(), t + contentView.getMeasuredHeight());
        }
    }

    private void ensurePlaceholderView() {
        if (mParallaxTarget != null && mPlaceholderView == null) {
            // To avoid repetition and add PlaceholderView
            ViewGroup targetView = (ViewGroup) mParallaxTarget;
            View view = targetView.findViewById(R.id.psvPlaceholderView);
            if (view != null) {
                targetView.removeView(view);
            }
            int width = mParallaxHolder.getRealWidth();
            int height = getOriginalHeight();
            ViewGroup.LayoutParams params = generatePlaceholderLayoutParams(mParallaxTarget, width, height);
            // Due to system {@link android.widget.Space} minimum compatible to API 14,
            // the library minimum support API 9, so rewrite the View
            mPlaceholderView = new PlaceholderView(getContext());
            mPlaceholderView.setLayoutParams(params);
            mPlaceholderView.setId(R.id.psvPlaceholderView);
            ParallaxScrollObserver observer = (ParallaxScrollObserver) mParallaxTarget;
            observer.setPlaceholder(mPlaceholderView);
            observer.setScrollCallback(this);
        }
    }

    private ViewGroup.LayoutParams generatePlaceholderLayoutParams(@NonNull View observer, int width, int height) {
        return observer instanceof AbsListView ? new AbsListView.LayoutParams(width, height)
                : new ViewGroup.LayoutParams(width, height);
    }

    private int getOriginalHeight() {
        return tryParallaxHolder()
                ? mParallaxHolder.getRealHeight() - mParallaxHolder.getParallaxTop()
                        - mParallaxHolder.getParallaxBottom()
                : 0;
    }

    private int getMaxPlaceholderHeight() {
        return tryParallaxHolder() ? mParallaxHolder.getRealHeight() - mParallaxHolder.getParallaxTop() : 0;
    }

    private int getMaxScrollY() {
        return getMaxPlaceholderHeight() - getOriginalHeight();
    }

    private boolean tryParallaxHolder() {
        return mParallaxHolder != null;
    }

    @Override
    public void onParallaxScrollChanged(int scrollX, int scrollY, boolean isTouchEvent) {
        if (tryParallaxHolder()) {
            mParallaxHolder.onScrollChanged(scrollX, scrollY, isTouchEvent);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mInitializeMotion = mDownMotion = e.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            float dy = e.getY() - mInitializeMotion;
            if (dy > mTouchSlop && !Utils.canChildScrollUp(mParallaxTarget)) {
                return true;
            }
            break;
        }
        return super.onInterceptTouchEvent(e);
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent ev) {
        switch (ev.getAction()) {
        case MotionEvent.ACTION_MOVE:
            mDownMotion = ev.getY();
            float dy = Utils.minMax(mDownMotion - mInitializeMotion, 0, getMaxScrollY() * 2);
            if (mParallaxTarget != null) {
                final float scrollY = DECELERATE_INTERPOLATOR.getInterpolation(dy / getMaxScrollY() / 2) * dy / 2;
                ViewCompat.setTranslationY(mParallaxTarget, scrollY);
                onParallaxScrollChanged(0, (int) -scrollY, true);
            }
            return true;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            rollback();
            return true;
        }
        return super.onTouchEvent(ev);
    }

    /** When raised his finger roll back to in situ */
    private void rollback() {
        if (tryParallaxHolder()) {
            mParallaxHolder.onRollback();
        }
        if (hasPlaceReset()) {
            RollbackAnimation animation = new RollbackAnimation(mParallaxTarget, 0, getMaxScrollY());
            animation.setInterpolator(ROLLBACK_INTERPOLATOR);
            animation.setDuration(ROLLBACK_DURATION);
            animation.start();
        }
    }

    private void onRefresh() {
        if (mOnRefreshListener != null) {
            mOnRefreshListener.onRefresh();
        }
    }

    private boolean hasPlaceReset() {
        return tryParallaxHolder() && ViewCompat.getTranslationY(mParallaxTarget) > 0;
    }

    public void autoRefresh() {
        if (tryParallaxHolder() && mParallaxTarget != null) {
            AutoRefreshAnimation animation = new AutoRefreshAnimation(mParallaxTarget, getMaxScrollY());
            animation.setInterpolator(ROLLBACK_INTERPOLATOR);
            animation.setDuration(AUTO_REFRESH_DURATION);
            animation.start();
        }
    }

    class RollbackAnimation extends Animation implements Animation.AnimationListener {

        View mTargetView;
        float mTargetHeight;
        float mCurrentHeight;
        boolean isNeedRefresh = false;

        @Override
        public void start() {
            mTargetView.startAnimation(this);
        }

        RollbackAnimation(View view, float targetHeight, int maxHeight) {
            this.mTargetView = view;
            this.mTargetHeight = targetHeight;
            this.mCurrentHeight = ViewCompat.getTranslationY(view);
            this.isNeedRefresh = mCurrentHeight - mTargetHeight > (maxHeight - mTargetHeight) * mRefreshRatio;
            super.setAnimationListener(this);
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            ViewCompat.setTranslationY(mTargetView,
                    (mTargetHeight - (mTargetHeight - mCurrentHeight) * (1.0f - interpolatedTime)));
        }

        @Override
        public void onAnimationStart(Animation animation) {

        }

        @Override
        public void onAnimationEnd(Animation animation) {
            if (isNeedRefresh) {
                onRefresh();
            }
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    }

    class AutoRefreshAnimation extends Animation implements Animation.AnimationListener {

        View mTargetView;
        float mTargetValue;
        float mCurrentValue;

        @Override
        public void start() {
            mTargetView.startAnimation(this);
        }

        AutoRefreshAnimation(View target, float targetValue) {
            this.mTargetView = target;
            this.mTargetValue = targetValue;
            this.mCurrentValue = ViewCompat.getTranslationY(target);
            super.setAnimationListener(this);
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            float scrollY = mTargetValue - (mTargetValue - mCurrentValue) * (1.0f - interpolatedTime);
            ViewCompat.setTranslationY(mTargetView, scrollY);
            onParallaxScrollChanged(0, (int) -scrollY, false);
        }

        @Override
        public void onAnimationStart(Animation animation) {

        }

        @Override
        public void onAnimationEnd(Animation animation) {
            rollback();
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    }

    public void setOnRefreshListener(OnRefreshListener listener) {
        this.mOnRefreshListener = listener;
    }

    public void setRefreshRatio(float refreshRatio) {
        if (refreshRatio < .0f || refreshRatio > 1.0f) {
            throw new IllegalArgumentException("The refresh ratio only between 0.0 f to 1.0 f value");
        }
        this.mRefreshRatio = refreshRatio;
    }

    public void setParallaxMode(@NonNull ParallaxMode mode) {
        if (mParallaxMode != mode) {
            mParallaxMode = mode;
            if (tryParallaxHolder()) {
                mParallaxHolder.setParallaxMode(mParallaxMode);
            }
        }
    }
}