text:
package com.krt.refreshcontainerlib;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;

import com.krt.refreshcontainerlib.extra.OrdinaryRefreshStyle;
import com.krt.refreshcontainerlib.extra.view.OrdinaryLoadMoreView;
import com.krt.refreshcontainerlib.extra.view.OrdinaryRefreshView;

/**
 * Created by zehong.tang on 2017/4/6.

 * 为任意 View 提供垂直方向上的刷新和加载更多能力的容器,仅支持一个直接子 View(Content view) ,

 * 刷新风格和刷新时展示的 View 均可自定义。

 * {@link IRefreshLoadMoreCallback} 通知外部刷新或加载

 * {@link IRefreshStyle} 定制刷新风格

 * {@link IRefreshView} 获得本控件的刷新状态信息来更新自定义的刷新和加载 view

 * 不要使用 addView 的方法添加 view,使用 {@link #setRefreshView(View)}、{@link #setLoadMoreView(View)}、

 * {@link #setContentView(View)} 设置的 view 才会被布局。ContentView 亦可在 xml 文件中以直接子元素的形式设置

 * 注意一定要调用 {@link #execute()} 使所有配置生效。

 *
 * The RefreshContainer can customize your refresh style and refresh view, see
 * {@link IRefreshStyle}, {@link IRefreshView} .

 *
 * The RefreshContainer should be used whenever the user can refresh and/or load the
 * contents of a view via a vertical swipe gesture. This container should be made
 * the parent of the view that will be refreshed as a result of the gesture and
 * can only support one direct child.The activity that instantiates this view should
 * add an IRefreshLoadMoreCallback to be notified whenever the swipe to refresh or
 * load gesture is completed.
 *
 * NOTICE: You must call {@link #execute()} bring into effect.
 */
public class RefreshContainer extends ViewGroup implements NestedScrollingParent{
    /**
     * 空闲状态
     */
    public static final int STATE_IDLE = 0;

    /**
     * 拉动控件,慢慢显露刷新或加载更多View
     */
    public static final int STATE_PREPARE = 1;

    /**
     * 拉动距离未达到门槛值松手,取消 work
     */
    public static final int STATE_PREPARE_CANCEL = 2;

    /**
     * 拉动距离达到门槛值未松手
     */
    public static final int STATE_READY = 3;

    /**
     * 拉动距离达到门槛值松手,进入 work 前的控件自动归位状态
     */
    public static final int STATE_PREPARE_WORK = 4;

    /**
     * work 状态
     */
    public static final int STATE_WORK = 5;

    /**
     * work 完成
     */
    public static final int STATE_COMPLETE = 6;

    private int mWorkState = STATE_IDLE;

    /**
     * 顶部刷新 View ,需实现 IRefreshView 接口,用于滑动过程中更新 View
     */
    private View mRefreshView;

    /**
     * 底部加载更多 View ,需实现 IRefreshView 接口,用于滑动过程中更新 View
     */
    private View mLoadMoreView;

    /**
     * 内容 View,STATE_IDLE(非刷新加载状态)下展示的 View,仅支持一个 contentView,宽高建议 MATCH_PARENT
     */
    private View mContentView;

    /**
     * 刷新风格,实现 IRefreshStyle 接口即可自定义刷新风格
     */
    private IRefreshStyle mRefreshStyle;

    /**
     * 刷新或加载更多回调 listener
     */
    private IRefreshLoadMoreCallback mRefreshLoadMoreCallback;

    /**
     * 操作条件检查者,判断是否可以刷新或加载
     */
    private IRefreshChecker mRefreshChecker;
    private boolean mEnableRefresh = true;
    private boolean mEnableLoadMore = true;
    private NestedScrollingParentHelper mNestedParentHelper;

    private static final float TOUCH_SLOP = 16;
    private static final int DEFAULT_SCROLL_DURATION = 380;
    private ValueAnimator mScroller;
    private int mScrollDuration = DEFAULT_SCROLL_DURATION;

    /**
     * true:在一个完整周期中,用户释放手势后,将禁止任何触摸事件,包括 contentView

     * false:用户释放手指后,仅在 settle 过程中禁止触摸事件
     */
    private boolean mDisableTouchWhenWork = false;

    /**
     * 刷新区域的颜色。刷新区域指 mRefreshView 及之上的区域(如 mRefreshView 高度 300px,

     * 刷新时下拉滑动的距离超过 300px,将会看到底色,该底色可能与整个风格不符)

     * 设置该属性可平滑过渡到刷新区域,将 mRefreshView 的颜色延伸到之上的区域

     * -1 没有设置,将忽略
     */
    private int mRefreshAreaColor = -1;

    /**
     * 加载更多区域的颜色。加载更多区域指 mLoadMoreView 及之下的区域,可参考 {@link RefreshContainer#mRefreshAreaColor}

     * -1 没有设置,将忽略
     */
    private int mLoadMoreAreaColor = -1;

    public RefreshContainer(Context context) {
        super(context);
        init(context, null);
    }

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

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

    private void init(Context context, AttributeSet attrs){
        //TOUCH_SLOP = ViewConfiguration.get(context).getScaledTouchSlop();

        //像素值为整形。若使用 float ,在 scroll 或其他滚动 View 的操作中将损失小数部分
        //最后出现和预期位置相差几个到十几个像素
        mScroller = ValueAnimator.ofInt(0, 0); //ValueAnimator.ofFloat(0, 0);
        mScroller.addListener(new ScrollStateListener());
        mScroller.addUpdateListener(new ScrollUpdateListener());
        mScroller.setInterpolator(new DecelerateInterpolator());
        mNestedParentHelper = new NestedScrollingParentHelper(this);
        initAttribute(context, attrs);
    }

    private void initAttribute(Context context, AttributeSet attrs){
        if(null == attrs)
            return;

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RefreshContainer);
        mScrollDuration = typedArray.getInt(R.styleable.RefreshContainer_duration, DEFAULT_SCROLL_DURATION);
        mDisableTouchWhenWork = typedArray.getBoolean(R.styleable.RefreshContainer_disableTouchWhenWork, false);

        typedArray.recycle();
    }

    public int getWorkState(){
        return mWorkState;
    }

    public int getDuration(){
        return mScrollDuration;
    }

    public RefreshContainer setDuration(int duration){
        mScrollDuration = duration;
        return this;
    }

    /**
     * 参见 {@link RefreshContainer#mRefreshAreaColor}
     * @param color color
     * @return instance of RefreshContainer
     */
    public RefreshContainer setRefreshAreaColor(int color){
        mRefreshAreaColor = color;
        setWillNotDraw(false);
        return this;
    }

    /**
     * 参见 {@link RefreshContainer#mLoadMoreAreaColor}
     * @param color color
     * @return instance of RefreshContainer
     */
    public RefreshContainer setLoadMoreAreaColor(int color){
        mLoadMoreAreaColor = color;
        setWillNotDraw(false);
        return this;
    }

    public RefreshContainer setInterpolator(TimeInterpolator interpolator){
        mScroller.setInterpolator(interpolator);
        return this;
    }

    public RefreshContainer setDisableTouchWhenWork(boolean disable){
        mDisableTouchWhenWork = disable;
        return this;
    }

    public RefreshContainer setRefreshLoadMoreCallback(IRefreshLoadMoreCallback l){
        mRefreshLoadMoreCallback = l;
        return this;
    }

    public RefreshContainer setEnableRefresh(boolean enabled){
        mEnableRefresh = enabled;
        return this;
    }

    public RefreshContainer setEnableLoadMore(boolean enabled){
        mEnableLoadMore = enabled;
        return this;
    }

    /**
     * 若子 View 支持 NestedScrollingChild ,可以不设置本 checker,将默认使用 Nested 方式
     * @param l checker
     * @return this
     */
    public RefreshContainer setRefreshChecker(IRefreshChecker l){
        mRefreshChecker = l;
        return this;
    }

    public RefreshContainer setRefreshStyle(IRefreshStyle style){
        mRefreshStyle = style;
        return this;
    }

    public RefreshContainer setContentView(View contentView){
        mContentView = contentView;
        return this;
    }

    public RefreshContainer setRefreshView(View refreshView){
        mRefreshView = refreshView;
        return this;
    }

    public RefreshContainer setLoadMoreView(View loadMoreView){
        mLoadMoreView = loadMoreView;
        return this;
    }

    /**
     * 当配置好后必须调用该方法使配置生效
     */
    public void execute(){
        checkArguments();
        removeAllViews();

        int[] viewOrder = getViewOrder();
        for (int order : viewOrder) {
            switch (order) {
                case IRefreshStyle.VIEW_TYPE_REFRESH_VIEW: addViewWithLayoutParams(mRefreshView);
                    break;
                case IRefreshStyle.VIEW_TYPE_LOAD_MORE_VIEW: addViewWithLayoutParams(mLoadMoreView);
                    break;
                case IRefreshStyle.VIEW_TYPE_CONTENT_VIEW: addViewWithLayoutParams(mContentView);
                    break;
                default:
                    break;
            }
        }
    }

    private void checkArguments(){
        if(null == mRefreshStyle){
            mRefreshStyle = new OrdinaryRefreshStyle();
        }

        if(null == mRefreshView){
            mRefreshView = new OrdinaryRefreshView(getContext());
        }
        if(!(mRefreshView instanceof IRefreshView)){
            throw new IllegalArgumentException("---Refresh view must implements IRefreshView---");
        }

        if(null == mLoadMoreView){
            mLoadMoreView = new OrdinaryLoadMoreView(getContext());
        }
        if(!(mLoadMoreView instanceof IRefreshView)){
            throw new IllegalArgumentException("---Load more view must implements IRefreshView---");
        }
    }

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

    public void setRefreshingDelay(int ms){
        setRefreshingDelay(ms, true);
    }

    public void setRefreshingDelay(int ms, final boolean animation){
        postDelayed(new Runnable() {
            public void run() {
                setRefreshing(animation);
            }
        }, ms);
    }

    public void setRefreshing(boolean animation){
        if(!mEnableRefresh)
            return;

        isPullDown = true;
        mRefreshView.setVisibility(View.VISIBLE);
        setWorkState(STATE_PREPARE_WORK);
        if(animation) {
            smoothScrollYTo(0, -getRefreshThreshold());
        }else{
            onRefreshStateSettle(-getRefreshThreshold());
            onSettleFinish();
        }
    }

    public void setWorkCompleted(){
        int oldWorkState = getWorkState();
        setWorkState(STATE_COMPLETE);

        //如果在 mRefreshLoadMoreCallback 中直接 setWorkCompleted()
        //则需要延迟 updateUI
        //因为这种情况下在 smoothScrollYTo 中 mScroller.isRunning() 为 true,导致 onSettleFinish 内旁路
        if(STATE_PREPARE_WORK == oldWorkState){
            post(new Runnable() {
                public void run() {
                    updateUI();
                }
            });
        }else{
            updateUI();
        }
    }

    /**
     * 外部加载任务完成,使用该方法通知本控件更新状态。

     * @param runnable 若外部控件的加载结果需要在本控件回到 {@link #STATE_IDLE} 后再呈现,可使用该参数
     *                 封装呈现逻辑。
     */
    public void setWorkCompleted(Runnable runnable){
        setWorkCompleted();
        mRunnableWhenCycleEnd = runnable;
    }

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        this.right = right - left;
        this.bottom = bottom - top;
        layoutView(getPaddingLeft(), getPaddingTop(), this.right, this.bottom);
    }

    protected void onFinishInflate() {
        super.onFinishInflate();
        int refreshLoadMoreViewCount = (null == mRefreshView ? 0 : 1) + (null == mLoadMoreView ? 0 : 1);
        int contentViewCount = getChildCount() - refreshLoadMoreViewCount;
        if(contentViewCount > 1){
            throw new IllegalArgumentException("---RefreshContainer only can hold one content view---");
        }else if(1 == contentViewCount){
            mContentView = getChildAt(0);
        }
    }

    protected void onDraw(Canvas canvas) {
        drawDecorationColor(canvas);
        super.onDraw(canvas);
    }

    private int right;
    private int bottom;
    private final Paint decorationPaint = new Paint();
    private void drawDecorationColor(Canvas canvas) {
        if(-1 != mRefreshAreaColor && mRefreshView.getVisibility() == View.VISIBLE){
            decorationPaint.setColor(mRefreshAreaColor);
            canvas.drawRect(0, -bottom, right, 0, decorationPaint);
        }
        if(-1 != mLoadMoreAreaColor && mLoadMoreView.getVisibility() == View.VISIBLE){
            decorationPaint.setColor(mLoadMoreAreaColor);
            canvas.drawRect(0, bottom, right, 2 * bottom, decorationPaint);
        }
    }

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

        int childCount = getChildCount();
        LayoutParams layoutParams;
        View childView;
        int width;
        int height;
        for(int i = 0; i < childCount; i++){
            childView = getChildAt(i);
            layoutParams = childView.getLayoutParams();
            width = makeMeasureSpec(layoutParams.width, widthMeasureSpec);
            height = makeMeasureSpec(layoutParams.height, heightMeasureSpec);
            childView.measure(width, height);
        }
    }

    private int makeMeasureSpec(int childSize, int parentSize){
        int result;
        if(childSize >= 0) {
            result = MeasureSpec.makeMeasureSpec(childSize, MeasureSpec.EXACTLY);
        }else if(LayoutParams.MATCH_PARENT == childSize){
            result = MeasureSpec.makeMeasureSpec(parentSize, MeasureSpec.EXACTLY);
        }else if(LayoutParams.WRAP_CONTENT == childSize){
            result = MeasureSpec.makeMeasureSpec(parentSize, MeasureSpec.AT_MOST);
        }else{
            result = MeasureSpec.makeMeasureSpec(parentSize, MeasureSpec.UNSPECIFIED);
        }
        return result;
    }

    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return isNestedScrollMode() && 0 != (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL);
    }

    public void onNestedScrollAccepted(View child, View target, int axes) {
        mNestedParentHelper.onNestedScrollAccepted(child, target, axes);
    }

    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        final int distance = getMoveDistance();
        if((distance < 0 && dy > 0) //Refresh view "show" && finger move up
                || (distance > 0 && dy < 0)){ //Load more view "show" && finger move down
            handleMove(dy);
            consumed[1] = dy;
        }
        super.onNestedPreScroll(target, dx, dy, consumed);
    }

    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        final int distance = getMoveDistance();
        if(0 == distance){ // Neither refresh view nor load more view not shown
            if(null == isPullDown && 0 != dyUnconsumed) {
                if (dyUnconsumed < 0){
                    isPullDown = mEnableRefresh ? true : null;
                }else{
                    isPullDown = mEnableLoadMore ? false : null;
                }
            }
        }
        handleMove(dyUnconsumed);
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return 0 != getMoveDistance();
    }

    public void onStopNestedScroll(View child) {
        mNestedParentHelper.onStopNestedScroll(child);
        handleUp();
    }

    public int getNestedScrollAxes() {
        return mNestedParentHelper.getNestedScrollAxes();
    }

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(mScroller.isRunning() || (mDisableTouchWhenWork && STATE_WORK == getWorkState())) {
            switch(ev.getAction()){
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    interceptTouchEvent = false;
                    break;
                default:
                    break;
            }
            return false;
        }

        return super.dispatchTouchEvent(ev);
    }

    private boolean interceptTouchEvent = false;
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if(isNestedScrollMode())
            return false;
        if(interceptTouchEvent)
            return true;

        if(STATE_WORK != getWorkState() && isReadyForWork()) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    startX = event.getX();
                    lastY = event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    if(shouldIntercept(event)) {
                        event.setAction(MotionEvent.ACTION_DOWN);
                        interceptTouchEvent = true;
                        return true;
                    }
                    break;
            }
        }
        return super.onInterceptTouchEvent(event);
    }

    private boolean shouldIntercept(MotionEvent event){
        float offsetY = lastY - event.getY();
        if(isValidTouch(startX - event.getX(), offsetY)){
            if(offsetY < 0){
                if(isReadyForRefresh()) {
                    isPullDown = true;
                    return true;
                }
            }else{
                if(isReadyForLoadMore()){
                    isPullDown = false;
                    return true;
                }
            }
        }
        return false;
    }

    private void attemptDetectDirection(MotionEvent event){
        shouldIntercept(event);
    }

    public boolean onTouchEvent(MotionEvent event) {
        if(isReadyForWork()){
            handleTouchEvent(event);
            return true;
        }
        return super.onTouchEvent(event);
    }

    private float startX;
    private float lastY;
    /**
     * null : STATE_IDLE 或 方向错误

     * true : 下拉(refresh)

     * false : 上拉(load more)
     */
    private Boolean isPullDown = null;
    private void handleTouchEvent(MotionEvent event){
        switch(event.getAction()){
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                startX = 0;
                lastY = 0;
                handleUp();
                interceptTouchEvent = false;
                break;
            case MotionEvent.ACTION_DOWN:
                startX = event.getX();
                lastY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //isPullDown 需要多次 onInterceptTouchEvent 才能判断出来,如果子 View 没有消费 touchEvent,
                //将极有可能没有判断出方向,这种情况需要在这里继续(多次)判断
                if(null == isPullDown){
                    attemptDetectDirection(event);
                }else{
                    handleMove(lastY - event.getY());
                }
                lastY = event.getY();
                break;
            default:
                break;
        }
    }

    private void handleMove(float offsetY){
        if(null == isPullDown) {
            return;
        }

        if(STATE_WORK != getWorkState()) {
            boolean isReachThreshold = isPullDown ? isReachRefreshThreshold() : isReachLoadMoreThreshold();
            setWorkState(isReachThreshold ? STATE_READY : STATE_PREPARE);
        }

        if(isPullDown){
            onRefreshStateMove(offsetY);
        }else{
            onLoadMoreStateMove(offsetY);
        }
    }

    private boolean isValidTouch(float offsetX, float offsetY){
        if(null != isPullDown)
            return true;

        final float x = Math.abs(offsetX);
        final float y = Math.abs(offsetY);
        if((x < TOUCH_SLOP && y < TOUCH_SLOP) || (x > y))
            return false;

        return true;
    }

    private void handleUp(){
        switch(getWorkState()){
            default:
                return;
            case STATE_PREPARE: setWorkState(STATE_PREPARE_CANCEL);
                break;
            case STATE_READY:
                if(null != mRefreshLoadMoreCallback){
                    setWorkState(STATE_PREPARE_WORK);
                }else{
                    setWorkState(STATE_COMPLETE);
                }
                break;
            case STATE_WORK: //处理在 work 状态时,再次下拉或上拉的情况
                if(getMoveDistance() == (isPullDown ? -getRefreshThreshold() : getLoadMoreThreshold())){
                    //在 work 状态,点击子 view 嵌套滑动时会先调用 onStopNestedScroll 一次(未知原因)
                    //导致 mScroller.isRunning() 为 true,在 dispatchTouchEvent 中丢失掉一些滑动数据
                    return;
                }
                break;
        }

        updateUI();
    }

    private void updateUI(){
        switch(getWorkState()){
            default:
                return;
            case STATE_PREPARE_CANCEL: smoothScrollYTo(getMoveDistance(), 0);
                break;
            case STATE_WORK: //处理在 work 状态时,再次下拉或上拉的情况
            case STATE_PREPARE_WORK:
                if(null == isPullDown) {
                    return;
                }
                smoothScrollYTo(getMoveDistance(), isPullDown ? -getRefreshThreshold() : getLoadMoreThreshold());
                break;
            case STATE_COMPLETE:
                notifyRefreshableView();
                smoothScrollYTo(getMoveDistance(), 0, getStartDelay());
                break;
        }
    }

    private void smoothScrollYTo(float startY, float targetY){
        smoothScrollYTo(startY, targetY, 0);
    }

    private void smoothScrollYTo(float startY, float targetY, int delayMs){
        settleLastValue = (int)startY;

        if(mScroller.isRunning()){
            isCanceled = true;
            mScroller.cancel();
        }
        mScroller.setIntValues((int)startY, (int)targetY); //mScroller.setFloatValues(startY, targetY);
        mScroller.setDuration(getDuration());
        mScroller.setStartDelay(delayMs);
        mScroller.start();
    }

    private int getStartDelay(){
        if(null == isPullDown) {
            return 0;
        }

        IRefreshView view = (IRefreshView)(isPullDown ? mRefreshView : mLoadMoreView);
        return view.getDurationForCompletedAnimation();
    }

    private boolean isReadyForRefresh(){
        return mEnableRefresh && (isNestedScrollMode() || mRefreshChecker.checkCanDoRefresh(this, mContentView));
    }

    private boolean isReadyForLoadMore(){
        return mEnableLoadMore && (isNestedScrollMode() || mRefreshChecker.checkCanDoLoadMore(this, mContentView));
    }

    private boolean isReadyForWork(){
        return isReadyForRefresh() || isReadyForLoadMore();
    }

    /**
     * isNestedScrollMode
     * @return true: 使用 nested 滑动

     * false:使用 mRefreshChecker 配合 onTouchEvent 滑动
     */
    private boolean isNestedScrollMode(){
        return null == mRefreshChecker;
    }

    private void setWorkState(int state){
        mWorkState = state;
    }

    //---------------------------以下方法是对 mRefreshStyle 封装---------------------------

    private void layoutView(int left, int top, int right, int bottom){
        if(null != mRefreshStyle) {
            mRefreshStyle.layoutView(mRefreshView, mLoadMoreView, mContentView, left, top, right, bottom);
        }
    }

    private void onRefreshStateMove(float offset){
        mRefreshView.setVisibility(View.VISIBLE);
        mRefreshStyle.onRefreshStateMove(getWorkState(), mRefreshView, mContentView, (int)offset);
        notifyRefreshableView();
    }

    private void onLoadMoreStateMove(float offset){
        mLoadMoreView.setVisibility(View.VISIBLE);
        mRefreshStyle.onLoadMoreStateMove(getWorkState(), mLoadMoreView, mContentView, (int)offset);
        notifyRefreshableView();
    }

    private void notifyRefreshableView(){
        if(null == isPullDown) {
            return;
        }

        IRefreshView refreshView;
        float threshold;
        if(isPullDown){
            refreshView = (IRefreshView) mRefreshView;
            threshold = getRefreshThreshold();
        }else{
            refreshView = (IRefreshView) mLoadMoreView;
            threshold = getLoadMoreThreshold();
        }

        refreshView.onPull(getWorkState(), isReachMaxDistance() ? getMaxDistance() : getMoveDistance(), threshold);
    }

    private void onRefreshStateSettle(float offset) {
        mRefreshStyle.onRefreshStateSettle(getWorkState(), mRefreshView, mContentView, offset);
    }

    private void onLoadMoreStateSettle(float offset) {
        mRefreshStyle.onLoadMoreStateSettle(getWorkState(), mLoadMoreView, mContentView, offset);
    }

    private int[] getViewOrder(){
        return mRefreshStyle.getViewOrder();
    }

    private boolean isReachRefreshThreshold(){
        return mRefreshStyle.isReachRefreshThreshold();
    }

    private boolean isReachLoadMoreThreshold(){
        return mRefreshStyle.isReachLoadMoreThreshold();
    }

    private int getMoveDistance(){
        return mRefreshStyle.getMoveDistance();
    }

    private float getRefreshThreshold() {
        return mRefreshStyle.getRefreshThreshold();
    }

    private float getLoadMoreThreshold() {
        return mRefreshStyle.getLoadMoreThreshold();
    }

    private void resetRefreshStyle(){
        mRefreshStyle.resetRefreshStyle(mRefreshView, mLoadMoreView, mContentView);
    }

    private int getMaxDistance(){
        return mRefreshStyle.getMaxDistance(isPullDown);
    }

    private boolean isReachMaxDistance(){
        return mRefreshStyle.isReachMaxDistance(isPullDown);
    }

    //---------------------------以上方法是对 mRefreshStyle 封装---------------------------

    private void addViewWithLayoutParams(View view){
        if(null == view.getLayoutParams()){
            view.setLayoutParams(getDefaultLayoutParams());
        }
        addView(view);
    }

    private LayoutParams getDefaultLayoutParams(){
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    }

    private void updateRefreshableViewVisibility(){
        final int distance = getMoveDistance();
        mRefreshView.setVisibility(distance < 0 ? View.VISIBLE : View.INVISIBLE);
        mLoadMoreView.setVisibility(distance > 0 ? View.VISIBLE : View.INVISIBLE);
    }

    private Runnable mRunnableWhenCycleEnd;
    private void onCycleEnded(){
        if(null != mRunnableWhenCycleEnd){
            mRunnableWhenCycleEnd.run();
        }
        mRunnableWhenCycleEnd = null;
    }

    private void onSettleFinish(){
        if(isCanceled){
            isCanceled = false;
            return;
        }

        switch(getWorkState()){
            default:
                return;
            case STATE_PREPARE_WORK:
                if(null == mRefreshLoadMoreCallback || null == isPullDown) {
                    setWorkCompleted();
                    break;
                }
                if(isPullDown)
                    mRefreshLoadMoreCallback.onRefresh();
                else
                    mRefreshLoadMoreCallback.onLoadMore();
                //如果在 mRefreshLoadMoreCallback 中直接 setWorkCompleted()
                //已经将 workState 设置为 STATE_COMPLETE, 则不能再 setWorkState(STATE_WORK)
                if(STATE_PREPARE_WORK == getWorkState()) {
                    setWorkState(STATE_WORK);
                }
                break;
            case STATE_COMPLETE: setWorkState(STATE_IDLE);
                break;
            case STATE_PREPARE_CANCEL: setWorkState(STATE_IDLE);
                break;
        }

        notifyRefreshableView();
        if(STATE_IDLE == getWorkState()){
            resetRefreshStyle();
            updateRefreshableViewVisibility();
            isPullDown = (0 == getMoveDistance() ? null : isPullDown);
            onCycleEnded();
        }
    }

    private void onSettle(float offset){
        if(null == isPullDown) {
            return;
        }

        if(isPullDown){
            onRefreshStateSettle(offset);
        }else{
            onLoadMoreStateSettle(offset);
        }
    }

    private int settleLastValue = 0;
    private class ScrollUpdateListener implements ValueAnimator.AnimatorUpdateListener {
        public void onAnimationUpdate(ValueAnimator animation) {
            int currentValue = (int)animation.getAnimatedValue();
            onSettle(currentValue - settleLastValue);
            notifyRefreshableView();
            settleLastValue = currentValue;
        }
    }

    private boolean isCanceled = false;
    private class ScrollStateListener extends AnimatorListenerAdapter{
        public void onAnimationEnd(Animator animation) {
            onSettleFinish();
        }
    }

}


All our mountain holidays favorites!
For successful summer holidays in the mountains, book your accommodation to Oz en Oisans in Northern Alps massive, Isère. This mountain resort will offer you exceptionnal areas and surprising landscapes. Your summer accommodation to Oz en Oisans will be for you a moment of wellness in the heart of a beautiful nature and you will spend holidays in wide open spaces. Our moutain offers will make you live warm emotions with all your family. We propose you quality holidays accommodation in all French massives, in the heart of mountains. We have many residences to rent for your summer to Oz en Oisans. Let yourself be carried by a feeling of happiness, enjoy the summer holidays in the mountains full of sun and discoveries !
In the heart of the Oz en Oisans ski resort you will find the "Couleurs Soleil" residence (with elevator) which harmoniously combines traditional, noble materials of Oisans: wood and stone. The Valley of Eau d'Olle where the Oz ski resort was built is one of the 6 valleys of the Oisans. At 1350 m and with the Grand Rousses mountain range overlooking it to the south, Oz en Oisans has a spectacular view of the Belledone mountains.
The residence Chalets des Neiges is situated in the heart of the Oz en Oisans village. Original design, built in stone and wood. Oz en Oisans village is perfect for holidays with family. The comfort and the atmosphere of the residence will make you feel at home! 2=1 SPECIAL SUMMER OFFER, ONLY ON SOME APARTMENTS FOR 4 PEOPLE, 4 ROOM APARTMENT FOR 6 PEOPLE, 5 ROOM APARTMENT FOR 8 PEOPLE, 6 ROOM APARTMENT FOR 10 PEOPLE AND 7 ROOM APARTMENT FOR 12 PEOPLE - valid until 31/05.
Les Cristaux, modern and bright holiday village, welcomes you warmly in the middle of the pines, in the center of the Oz en Oisans resort, 50 m from shops. On site, you will enjoy spacious rooms, a dining room and a large heated indoor pool with panoramic views of the great peak of Belledonne. The conviviality, the relaxation and the pleasure are the key words of your stay thanks to the numerous activities proposed. You will be enchanted by the tranquility, the comfort and the maximum sunshine of the establishment. This cocoon village offers 44 rooms (2-7 persons) distributed in the main building and 4 apartments in an annex building.
The residence La Cascade - Les Epinettes is located in the centre of the village, near the shops and restaurants. It is compose of 15 apartments on 2 buildings of 3 floors and 1 building of 5 floors with elevator.
The residence Les Gemeaux I, with lift, is located at 500m from the heart of the resort of Villard de Lans. The Swimming pool ice rink is at 5 minutes by walk from the residence. You can use the hiking trails ways to make nice walk in the heart of the Natural Park of the Vercors.
The 2 chalets are located in Venosc, linked to Les 2 Alpes by the gondola lift of Venosc, 250 m away from this accommodation. The chalet is fully equipped with kitchen, bathrooms, washing machine, television, balcony and garden. You can enjoy the heated indoor swimming pool and the sauna.
The Chalet Harmonie is situated in "Les 2 Alpes" resort. It is located 25 metres away from shops. The chalet is a semi-detached chalet with the Chalet Marie. This accommodation is fully equipped. You can take advantage of the terrace of 40 m².
The high quality residence l'Alba, with a lift, is located on the "Pied Moutet" side. The residence offers a digital lock, an automatic light working 7 days a week and 24 hours a day and a vidéo surveillance. A free outdoor car park is at your disposal (limited number of parking space) and it possible to rent a covered parking space subject to availability. On the ground floor, you will find the reception and a bike shed during the summer season. The cosy apartments are equipped with a satellite and LCD TV, DVD player, individual safe, paying WiFi access and bathroom and bed linen are included.


text:
en.wikipedia.org › wiki › ABC_RecordsABC Records - Wikipedia
en.wikipedia.org › wiki › ABC_Records
ABC Records was an American record label founded in New York City in 1955. It originated as the main popular music label operated by the Am-Par Record Corporation. Am-Par also created the Impulse! jazz label in 1960. It acquired many labels before ABC was sold to MCA Records in 1979.
Defunct: 1979
Parent company: American Broadcasting Company
www.discogs.com › label › 12220-ABC-RecordsABC Records Label | Releases | Discogs
www.discogs.com › label › 12220-ABC-Records
For unofficial releases/counterfeit/bootlegs, please use ABC Records (5). Use ABC Recording Studios for the recording location. In 1955, American Broadcasting-Paramount Theaters, Inc. formed a subsidiary company called Am-Par Record Corp. which began to release pop, jazz, rhythm and blues, children's and spoken word records under the...
Title ( Format)
1C 006-96868
'Til The World Ends  (7")
ABC Records, EMI Electrola
1 C 052-90 808
East Broadway Run Down  (LP, Album)
Rock 'N Roll Power  (LP, Album, Comp)
ABC Records, HÖR ZU
1C 054-96 507
From The Beginning / Twelve Great ...
ABC Records
See full list on discogs.com
Offer Count: 117K
www.abcrecord.com › enABC(Int`l)Records
www.abcrecord.com › en
ABC (Int'l) Records only produce high quality audiophile record! We sincerely looking forward to your appreciation and recognition. RMG SM911 1/4" X 2500' 10.5" Metal Reel Box
logos.fandom.com › wiki › ABC_RecordsABC Records | Logopedia | Fandom
logos.fandom.com › wiki › ABC_Records
ABC (United States) Universal Music Group. The Walt Disney Company. 1955. 1965. Defunct record labels. Record labels of the United States. EMI. ABC Records.
What was the original name of ABC Records?
The label was initially called Am-Par Records (1955), but quickly changed to ABC-Paramount Records (1955–1966), and then renamed ABC Records in 1966. American Broadcasting-Paramount Theatres (AB-PT) was the owner of the American Broadcasting Company (ABC).
ABC Records - Wikipedia
en.wikipedia.org/wiki/ABC_Records
What did ABCABC records do in the 1960s?
ABC Records sub-labeled Apt to release singles. In the early 1960s, it bought Westminster, a classical music label. For jazz it created Impulse!. Led by Creed Taylor and Bob Thiele, Impulse! developed a reputation for innovative releases, including albums by John Coltrane from 1961 until his death in 1967.
What happened to ABC Records in Canada?
GRT handled ABC until 1979 when GRT went bankrupt and MCA bought the label, thereafter its catalogue was handled in Canada by MCA Records (Canada) .
ABC Records Label | Releases | Discogs
www.discogs.com/label/12220-ABC-Records
Who was the last president of ABC Records?
The company's last president, Steve Diener, was named president in 1977 after serving as head of ABC Records' international division. Because of financial problems except for its Nashville office, ABC Records was sold on January 31, 1979 to MCA Records with ABC Records being its third label likely under a different name.
www.discogs.com › label › 139422-ABC-Records-3ABC Records (3) Label | Releases | Discogs
www.discogs.com › label › 139422-ABC-Records-3
479542. The Don Burrows Quintet With The Adelaide Connection. The Don Burrows Quintet With The Adelaide Connection -. Nice 'n' Easy  (CD, Album) ABC Records (3) 479542. Australia. 1992.
2ABCL8107
The Saturday Show  (2xLP)
2ABCL 8113
Queensland Symphony Orchestra
The Nutcracker Ballet In Two Acts, Op. 71 ...
2ABC L 8206
The Big Broadcast 1932-1982  (2xLP)
David Rex Quartet
The Dark Side Of The Street  (CD, ...
Offer Count: 1.1K
abcrecords.azurewebsites.net › CoProviders › IndexIndex - ABC Records
abcrecords.azurewebsites.net › CoProviders › Index
abc@scuba-repairs.com.au 0408 369 664 Edit | Details | Delete | Renewals: Gary Pratt garyskeet@hotmail.com 0437 728 157 ...
IN FULL: Victoria records 324 cases of COVID-19 | ABC News
NSW records highest ever number of daily cases in Australia | ABC News
Searches related to ABC Records
ABC Records inc ABC Records int'l
sam clark ABC Records ABC Records australia
ABC Records logo paramount records 1969
ABC Records discography abc dunhill records
Ads · ABC Records
Impressions: The Fabulous Abc Records 12" Lp 33 Rpm
Abc Records Rhythm Heritage 45 Record
Abc Records Other | The Grass Roots-Move Along Vintage Record Album Lp | Color: Black | Size: 12 Inch 33 Rpm
Barbara MANDRELL "MOODS" 12" 33Rpm Vinyl Album Abc Records 1978 Country B17
Abc Records Lp Inner Sleeve (Circa 1970S)
Abc Records Media | Vintage 'New Dimensions In Stereo' Vinyl Record Box Set Of 5-Command,Abc Records | Color: Black/Red | Size: Os
James Gang Live In Concert US 1971 ABCX 733 ABC Records Vinyl Album
Vintage Records, ABC, The LEXICON Of LOVE, Abc Records, Abc Albums, Abc Vinyl, Abc Lp, Vinyl Lp, Vintage Vinyl, Records, Vinyl, 1982 Records
US record label; imprint of ABC Records, Inc.
ABC Records was an American record label founded in New York City in 1955. It originated as the main popular music label operated by the Am-Par Record Corporation. Am-Par also created the Impulse! jazz label in 1960. It acquired many labels before ABC was sold to MCA Records in 1979. ABC produced music in a variety of genres: pop, rock, jazz, country, rhythm and blues,... en.wikipedia.org
MCA Records
Dot Records
20th Century Fox Records
MGM Records
United Artists Records
Mercury Records
Decca Records

redpajama_set_name: RedPajamaCommonCrawl


text:
James Cameron Slams Terminator and Alien Sequels
Whether he is seeing his Avatar world expand into a theme park or engaging in a Hollywood feud with Avengers: Infinity War star Josh Brolin, there is always plenty happening to occupy the mind of James Cameron. The 62-year old takes his filmography very seriously, particularly when it comes to two of his most famous and influential pieces of work - Aliens and The Terminator.
The Canadian would write and direct the 1984 science-fiction blockbuster to launch Arnold Schwarzenegger further into superstardom, while he would use that feature to helm the 1986 sequel to Ridley Scott's masterpiece Alien. Speaking with The Daily Telegraph this week, the filmmaker pulled no punches for both properties. As far as he is concerned, the ball has been dropped.
Compatriots Struggle Showcases The Brilliance of My Work: JC
Making something of a humblebrag, the Ontario native was quizzed about his reception to 2017's Alien: Covenant. Arriving with huge expectations to reinvigorate the series following Prometheus, Cameron offered a blunt assessment.
"The subsequent Alien films haven't been as good as Alien and Aliens," he argued. "They set a standard the other movies were judged against. That's true of the Terminator films as well. It doesn't diminish the original accomplishment in any way. If anything it makes the original seem better by comparison."
Having to admit that he had a hand in Terminator Genisys, Cameron said that he was limited by studio interference and could not offer the director the type of advice they required.
"That doesn't mean I bet against the new films – I go 'all right guys, let's see the magic'," he states. "I've been disappointed. The problem is, I'm so damn analytical I can point out exactly what didn't work. If I had 20 minutes with the filmmaker ahead of time, I might have been able to help. But that's just not how this business works."
Truth is Stranger Than Fiction For Rebooted Terminator
Switching outlets to IGN, the veteran talked about his plans for Terminator 6 and what that narrative would entail. Seeing Arnold Schwarzenegger return to explain his link to the T-800 prototype and Skynet, Cameron will link up with Deadpool's Tim Miller to portray a world that is not too dissimilar from reality.
"When I became aware of the fact that I could back into a control position on the rights, then I started to ask myself artistically, 'Is there anything there? Is there anything to be said that I haven't already said, and that would even be relevant in the 2020s (when these hypothetical films would come out)?' I thought, 'Well, let's look at that.' I mean, a lot of the things that were science-fiction in Terminator are now around us - and actual discussions on the ethics of having a robot have its own kill decision possibilities, things like that. It's actually happening. So okay, maybe there is room for a film that examines these themes. It just has to be retooled for an audience's expectations now."
Source: NME, IGN
14 August 2017 August 14th, 2017
Terminator 6 Report Reveals Dani Ramos Details and Mexico City Shoot
Terminator Salvation Role A Great Regret For Christian Bale
Gal Gadot: I Didn't Want To Publicly Acknowledge James Cameron's Comments
Official: Terminator 6 Throwing Out Everything Post T2


text:
Q: How to view a post from the wordpress admin screen I have been thinking of displaying my post from the admin dashboard screen just like how a plugin is displayed.

Above is a wordpress dashboard. How do i view post from the wp screen. Kindly help me. without being redirected to the post url I'll appreciate

A: Add this code to the functions
///Show on the counter
function my_custom_dashboard_widgets() {
global $wp_meta_boxes;
wp_add_dashboard_widget('custom_help_widget', 'Posts','custom_dashboard_help');
}
function custom_dashboard_help() {
$randoms= new WP_Query(array(
'post_type'=>'post','post_status'=>'publish','order'=>'DESC','orderby'=>'ID',
'posts_per_page'=>'10'));
if($randoms->have_posts()) : while($randoms->have_posts()) : $randoms->the_post();?>

<img src="<?php
$thumb_id = get_post_thumbnail_id();
$thumb_url = wp_get_attachment_image_src($thumb_id,'thumbnail-size', true);
echo $thumb_url[0];
?>" alt=""  style="width: 50px;float: right;height: 50px;">

<a href="<?php the_permalink(); ?>" target="_blank" ><?php the_title(); ?>

<a href="<?php bloginfo('url');?>/wp-admin/post.php?post=<?php the_ID(); ?>&action=edit"style="color: blue;" target="_blank" > edit

<?php endwhile; endif; wp_reset_query();
 } add_action('wp_dashboard_setup', 'my_custom_dashboard_widgets');

redpajama_set_name: RedPajamaStackExchange


This couple is such an amazingly sweet couple. They are going to make fantastic parents to their baby girl Rylan. I was excited to see they brought along their first baby Kingston. He is such a beautiful little dog and I had so much fun shooting him.


text:
namespace theoria {

TEST(TestComponent, TestBasics) {

    core::Component def ;
    EXPECT_EQ(def.id(), -1) ;
    EXPECT_EQ(def.name(), "") ;
    def.setName("Default") ;
    EXPECT_EQ(def.name(), "Default") ;
    config::Config c ;
    EXPECT_EQ(def.init(c), core::Dependencies()) ;
    //For code coverage of default impls. These do nothing
    std::vector<core::Component*> v ;
    def.finalize(v) ;
    def.appLifeCycle(core::AppLifeCycle::INITIALIZED) ;
    def.compLifeCycle(core::CompLifeCycle::DISABLED, 0) ;
    core::Message msg ;
    def.onMessage(msg) ;
}

class CalcInterface
{
public:

    virtual ~CalcInterface() ;

    virtual int calc(int x, int y) const = 0 ;
} ;

CalcInterface::~CalcInterface()
{
}

class CalcInterface2
{
public:

    virtual ~CalcInterface2() ;

    virtual int calc(int x, int y) const = 0 ;
} ;

CalcInterface2::~CalcInterface2()
{
}

/////////////////////
class ComponentImplCalcInterface : public CalcInterface, public core::Component
{
public:

    int calc(int x, int y) const override ;
} ;

int ComponentImplCalcInterface::calc(int x, int y) const
{
    return x * y ;
}

TEST(TestComponentImplCalcInterface, TestCast) {
   ComponentImplCalcInterface comp ;

   ASSERT_NE(comp.cast<CalcInterface>(), nullptr) ;
   EXPECT_EQ(comp.cast<CalcInterface>()->calc(2,2), 4) ;
   EXPECT_THROW(comp.cast<CalcInterface2>(), std::runtime_error) ;
}

/////////////////////
class ComponentOwnsCalcInterface : public core::Component
{

    class CalcInterfaceImpl : public CalcInterface
    {
    public:

        int calc(int x, int y) const override ;
    } ;

public:

    ComponentOwnsCalcInterface() ;

    core::Component* acquire(const std::type_info& type, void ** dest) override ;

private:

    std::unique_ptr<CalcInterface> p_impl ;

} ;

int ComponentOwnsCalcInterface::CalcInterfaceImpl::calc(int x, int y) const
{
    return x+y ;
}

ComponentOwnsCalcInterface::ComponentOwnsCalcInterface()
{
    p_impl = std::make_unique<ComponentOwnsCalcInterface::CalcInterfaceImpl>() ;
}

core::Component* ComponentOwnsCalcInterface::acquire(const std::type_info& type, void ** dest)
{
    *dest = nullptr ;
    if (type == typeid(CalcInterface)) {
        *dest = p_impl.get() ;
    }
    return nullptr;
}

TEST(TestComponentOwnsCalcInterface, TestCast) {
   ComponentOwnsCalcInterface comp ;

   ASSERT_NE(comp.cast<CalcInterface>(), nullptr) ;
   EXPECT_EQ(comp.cast<CalcInterface>()->calc(3,2), 5) ;
   EXPECT_THROW(comp.cast<CalcInterface2>(), std::runtime_error) ;
}

/////////////////////
class ComponentKnowsCalcInterfaceImpl : public core::Component
{
public:

    core::Component* acquire(const std::type_info& type, void ** dest) override ;

private:

    ComponentImplCalcInterface _impl ;
} ;

core::Component* ComponentKnowsCalcInterfaceImpl::acquire(const std::type_info& type, void ** dest)
{
    *dest = nullptr ;
    return &_impl ;
}

TEST(TestComponentKnowsCalcInterfaceImpl, TestCast) {
   ComponentKnowsCalcInterfaceImpl comp ;

   ASSERT_NE(comp.cast<CalcInterface>(), nullptr) ;
   EXPECT_EQ(comp.cast<CalcInterface>()->calc(2,3), 6) ;
   EXPECT_THROW(comp.cast<CalcInterface2>(), std::runtime_error) ;
}

/////////////////////
class ComponentKnowsCalcInterfaceOwn : public core::Component
{
public:

    core::Component* acquire(const std::type_info& type, void ** dest) override ;

private:

    ComponentOwnsCalcInterface _impl ;
} ;

core::Component* ComponentKnowsCalcInterfaceOwn::acquire(const std::type_info& type, void ** dest)
{
    *dest = nullptr ;
    return &_impl ;
}

TEST(TestComponentKnowsCalcInterfaceOwn, TestCast) {
   ComponentKnowsCalcInterfaceOwn comp ;

   ASSERT_NE(comp.cast<CalcInterface>(), nullptr) ;
   EXPECT_EQ(comp.cast<CalcInterface>()->calc(2,3), 5) ;
   EXPECT_THROW(comp.cast<CalcInterface2>(), std::runtime_error) ;
}

}

redpajama_set_name: RedPajamaGithub


We help them. They help us. It's an important relationship for us both. LMCU is proud to announce its annual Dealer of the Year award winners. Awards are based on loan volume, quality of transactions, accuracy of paperwork and commitment to positive representation of LMCU to the car-buying public. These exceptional dealerships consistently demonstrate and reflect our corporate philosophy of always doing what's best for our members. Congratulations to all!


Just like that noted Egyptologist, Howard Carter – the one who discovered King Tut's mummified remains surrounded by a breathtaking array of splendid treasures – except in my case, no treasures. All I'd come noseto- nose with in the dim tomb-like confines turned out to be the desiccated carcass of a cat that had never belonged to me. I bagged the corpse and dragged it out of the crawlspace. Then, sucking a few more gulps of fresh air, I went back in to fix a leaking water pipe.
They say there are no atheists in foxholes, a sentiment that must hold true for crawlspaces. As a boy raised in the Midwest, I experienced full basements. My subterranean subconscious tells me they still stand for stalwart tornado shelters, laundry rooms, workshops, and root cellars. But a crawlspace? Not until moving to the Southwest.
My catacomb-like expanse stretched out the length of our doublewide trailer, yet barely two feet high. Previous owners had skirted it with stacked field stones, then plastered the stones together with cement. The outside sunlight filtered through myriad holes and cracks where the cement had fallen away. Spiderwebs hung like beaded curtains. I absolutely did not want to be in that space and had avoided it for years. In fact, when we first purchased the place it required only a cursory peek behind the access flap to assure me that nothing could possibly go wrong under there!
Of course, sewer lines seep, pipes freeze, critters creep, and snakes slither. It's deterioration enhanced by human nature's practice to build as cheaply as possible. Clean crawlspaces actually exist, with vapor barriers and tight foundations footings, leveling and lighting, but not where I've lived. One home I inspected came with the equivalent of a bear den under the floor, another with a set of trenches straight out of the history books from WW I. Cortez enjoys an average of 240 sunny days annually, but there are dark places people don't talk about.
The only way to reach my water pipes required unrolling a long black avenue of sheet plastic which I pulled myself along, using its slickness against my back while grabbing the undercarriage of the mobile home's metal frame. Dust clouded my vision with each foot of progress. My partner in excavation stood somewhere above me tapping a broomstick against the kitchen floor, emitting a kind of wooden sonar designed to help me locate the offending leak. I wore a headlamp like a miner, its beam punctuating my fright each time it lighted on some creepy shadow off in the distance.
The dirt floor was littered with rusted cans, beer bottles, mason jars, useless lengths of pipe, a Barbie doll, crumpled and yellowed newspaper, and shards of tin, all amid the rubble of wood and concrete blocks that once propped up the trailer's frame. I began to suspect a hidden trapdoor existed in the floor above me where former residents simply dropped their trash. How else could this mess have arrived? Unless people host secret crawlspace gatherings, plotting the overthrow of governments, near and far. I wasn't sure.
With a shudder of despair I noticed that no cables had been secured to keep the trailer from flying away in a Wizardof- Oz-ish fashion, so I knew I'd be underneath again, tethering my worries. Then the critter agenda manifested itself, a gap in the stones where an unidentified flurry of furry critters had left evidence of their visitations. My headlamp swept the area like a searchlight, expecting to find a set of glowing eyes staring back at me. All I found was a dusty soda bottle tipped on its side, only the crescent of its bottom still visible above the soft dirt like a new moon on its way to ground.
Eventually I located and fixed the pipe, which proved to be much easier than I'd expected. The task required all my concentration, so immediately all the ghosts receded from my imagination as I shuttled back and forth along my plastic slide, retrieving the tools I needed.
But I should also mention the curse that manifested itself as a result of opening the crawlspace. Every time I even think about going back down there, trickles of sweat work their way down my temples. When I think about something else, they go away. Imagine that.


text:
#include "scripting/lua-bindings/manual/CCComponentLua.h"
#include <string>
#include "base/CCScriptSupport.h"
#include "platform/CCFileUtils.h"
#include "scripting/lua-bindings/manual/CCLuaEngine.h"
#include "scripting/lua-bindings/manual/LuaBasicConversions.h"

NS_CC_BEGIN

const std::string ComponentLua::ON_ENTER = "onEnter";
const std::string ComponentLua::ON_EXIT = "onExit";
const std::string ComponentLua::UPDATE = "update";

#define KEY_COMPONENT  "component"

namespace {
    void adjustScriptFileName(std::string& scriptFileName)
    {
        assert(scriptFileName.size() > 4);

        auto fileUtils = FileUtils::getInstance();
        if (fileUtils->isFileExist(scriptFileName))
            return;

        const std::string luaSuffix(".lua");
        const std::string luacSuffix(".luac");

        // xxx.lua -> xxx.luac or
        // xxx.luac -> xxx.lua
        if (scriptFileName.compare(scriptFileName.size() - luaSuffix.size(), luaSuffix.size(), luaSuffix) == 0)
            scriptFileName.replace(scriptFileName.size() - luaSuffix.size(), luaSuffix.size(), luacSuffix);
        else
            scriptFileName.replace(scriptFileName.size() - luacSuffix.size(), luacSuffix.size(), luaSuffix);
    }
}

int ComponentLua::_index = 0;

ComponentLua* ComponentLua::create(const std::string& scriptFileName)
{
    CC_ASSERT(!scriptFileName.empty());

    initClass();

    adjustScriptFileName(const_cast<std::string&>(scriptFileName));
    auto componentLua = new(std::nothrow) ComponentLua(scriptFileName);
    if (componentLua)
    {
        componentLua->autorelease();
    }

    return componentLua;
}

ComponentLua::ComponentLua(const std::string& scriptFileName)
: _scriptFileName(scriptFileName)
, _table(nullptr)
, _strIndex("")
{
    _succeedLoadingScript = loadAndExecuteScript();
}

ComponentLua::~ComponentLua()
{
    removeLuaTable();
}

void ComponentLua::getScriptObjectInternal() const
{
    lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
    lua_pushstring(l, KEY_COMPONENT);      // stack: "component"
    lua_rawget(l, LUA_REGISTRYINDEX);      // stack: LUA_REGISTRYINDEX["component"]
    lua_pushstring(l, _strIndex.c_str());  // stack: LUA_REGISTRYINDEX["component"] strIndex
    lua_rawget(l, -2);                     // stack: LUA_REGISTRYINDEX["component"]
}

void* ComponentLua::getScriptObject() const
{
    getScriptObjectInternal();
    return nullptr;
}

void ComponentLua::update(float delta)
{
    if (_succeedLoadingScript && getLuaFunction(ComponentLua::UPDATE))
    {
        getUserData();
        lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
        lua_pushnumber(l, delta);
        LuaEngine::getInstance()->getLuaStack()->executeFunction(2);
    }
}

void ComponentLua::onEnter()
{
    if (_succeedLoadingScript && getLuaFunction(ComponentLua::ON_ENTER))
    {
        getUserData();
        LuaEngine::getInstance()->getLuaStack()->executeFunction(1);
    }
}

void ComponentLua::onExit()
{
    if (_succeedLoadingScript && getLuaFunction(ComponentLua::ON_EXIT))
    {
        getUserData();
        LuaEngine::getInstance()->getLuaStack()->executeFunction(1);
    }
}

bool ComponentLua::getLuaFunction(const std::string& functionName)
{
    lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();

    lua_pushstring(l, KEY_COMPONENT);                // stack: "component"
    lua_rawget(l, LUA_REGISTRYINDEX);                // stack: table_of_component
    lua_pushstring(l, _strIndex.c_str());            // stack: table_of_component strIndex
    lua_rawget(l, -2);                               // stack: table_of_component table_of_this
    lua_pushstring(l, functionName.c_str());         // stack: table_of_component table_of_this "update"
    lua_rawget(l, -2);                               // stack: table_of_component table_of_this table_of_this["update"]
    lua_remove(l, -2);                               // stack: table_of_component table_of_this["update"]
    lua_remove(l, -2);                               // stack: table_of_this["update"]

    int type = lua_type(l, -1);
//    if (type != LUA_TFUNCTION)
//    {
//        CCLOG("can not get %s function from %s", functionName.c_str(), _scriptFileName.c_str());
//    }

    return type == LUA_TFUNCTION;
}

bool ComponentLua::loadAndExecuteScript()
{
    // register native functions
    auto engine = LuaEngine::getInstance();
    lua_State *l = engine->getLuaStack()->getLuaState();

    // load script
    auto fileUtils = FileUtils::getInstance();
    std::string fullPathOfScript = fileUtils->fullPathForFilename(_scriptFileName);
    Data data = fileUtils->getDataFromFile(fullPathOfScript);
    int error = LUA_ERRFILE;
    if(data.getSize() > 0)
        error = engine->getLuaStack()->luaLoadBuffer(l, (const char*)data.getBytes(), (int)data.getSize(), fullPathOfScript.c_str());
    if (error)
    {
        CCLOG("ComponentLua::loadAndExecuteScript: %s", lua_tostring(l, -1));
        lua_pop(l, 1);
        return false;
    }

    // execute script
    error = lua_pcall(l, 0, 1, 0);
    if (error)
    {
        CCLOG("ComponentLua::loadAndExecuteScript: %s", lua_tostring(l, -1));
        lua_pop(l, 1);
        return false;
    }

    // check the return value from lua script is a table
    int type = lua_type(l, -1);
    if (type != LUA_TTABLE)
    {
        CCLOG("%s should return a table, or the script component can not work currectly", _scriptFileName.c_str());
        return false;
    }

    storeLuaTable();
    return true;
}

void ComponentLua::initClass()
{
    static bool run = true;
    if (run)
    {
        // LUA_REGISTRYINDEX["component"] = new table

        LuaEngine* engine = LuaEngine::getInstance();
        lua_State* l = engine->getLuaStack()->getLuaState();

        lua_pushstring(l, KEY_COMPONENT);              // stack: "component"
        lua_newtable(l);                               // stack: "component" table
        lua_rawset(l, LUA_REGISTRYINDEX);              // stack: -

        run = false;
    }

}

void ComponentLua::storeLuaTable()
{
    lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();

    _index++;
    _strIndex.append(StringUtils::toString(_index));

    // LUA_REGISTRYINDEX["component"][strIndex] = table return from lua
    lua_pushstring(l, KEY_COMPONENT);          // stack: table_return_from_lua "component"
    lua_rawget(l, LUA_REGISTRYINDEX);          // stack: table_return_from_lua table_of_component
    lua_pushstring(l, _strIndex.c_str());      // stack: table_return_from_lua table_of_component strIndex
    lua_pushvalue(l, -3);                      // stack: table_return_from_lua table_of_component strIndex table_return_from_lua
    lua_rawset(l, -3);                         // stack: table_return_from_lua table_of_component
    lua_pop(l, 1);                             // stack: table_return_from_lua

    // add table's elements to userdata's metatable
    object_to_luaval<cocos2d::ComponentLua>(l, "cc.ComponentLua", this);  // stack: table_return_from_lua userdata
    lua_getmetatable(l, -1);                   // stack: table_return_from_lua userdata mt
    lua_remove(l, -2);                         // stack: table_return_from_lua mt
    lua_pushnil(l);                            // stack: table_return_from_lua mt nil
    while (lua_next(l, -3))                    // stack: table_return_from_lua mt key value
    {
        lua_pushvalue(l, -2);                  // stack: table_return_from_lua mt key value key
        lua_insert(l, -2);                     // stack: table_return_from_lua mt key key value
        lua_rawset(l, -4);                     // stack: table_return_from_lua mt key
    }
    lua_pop(l, 2);
}

void ComponentLua::removeLuaTable()
{
    if (_succeedLoadingScript)
    {
        // LUA_REGISTRYINDEX["component"][strIndex] = nil

        lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();

        lua_pushstring(l, KEY_COMPONENT);      // stack: "component"
        lua_rawget(l, LUA_REGISTRYINDEX);      // stack: LUA_REGISTRYINDEX["component"]
        lua_pushstring(l, _strIndex.c_str());  // stack: LUA_REGISTRYINDEX["component"] strIndex
        lua_pushnil(l);                        // stack: LUA_REGISTRYINDEX["component"] strIndex nil
        lua_rawset(l, -3);                     // stack: LUA_REGISTRYINDEX["component"]
    }
}

void ComponentLua::getUserData()
{
    lua_State *l = LuaEngine::getInstance()->getLuaStack()->getLuaState();
    object_to_luaval<cocos2d::ComponentLua>(l, "cc.ComponentLua",this);
}

NS_CC_END