org.androfarsh.widget.DragGridLayout.java Source code

Java tutorial

Introduction

Here is the source code for org.androfarsh.widget.DragGridLayout.java

Source

/*
 * 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.
 *
 * Authored by Anton Kuhlevskyi <anton.kuhleskiy@gmail.com>
 */
package org.androfarsh.widget;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.support.v4.view.GestureDetectorCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector.OnGestureListener;
import android.view.Gravity;
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.AnimationSet;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.view.animation.Animation.AnimationListener;

public class DragGridLayout extends ViewGroup {
    static class Cell {
        public final static int LEFT = 1;
        public final static int RIGHT = 2;
        public final static int TOP = 4;
        public final static int BOTTOM = 8;

        public final Rect rect;

        Cell left;

        Cell top;

        Cell right;

        Cell bottom;

        Cell() {
            rect = new Rect();
        }

        Cell(int x, int y, int s) {
            rect = new Rect(x, y, x + s, y + s);
        }

        public Cell getBottom() {
            return bottom;
        }

        public Cell getLeft() {
            return left;
        }

        public Cell getRight() {
            return right;
        }

        public Cell getTop() {
            return top;
        }

        @Override
        public int hashCode() {
            return (rect.top << 12) | rect.left;
        }

        public void setCell(Cell cell, int direction) {
            switch (direction) {
            case Cell.LEFT:
                left = cell;
                break;
            case Cell.TOP:
                top = cell;
                break;
            case Cell.RIGHT:
                right = cell;
                break;
            case Cell.BOTTOM:
                bottom = cell;
                break;
            }
        }

        @Override
        public String toString() {
            return rect.toString();
        }
    }

    private final class GestureListenerImpl implements OnGestureListener {
        OnGestureListener gestureListener;

        @Override
        public boolean onDown(MotionEvent e) {
            return false;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            Log.e(VIEW_LOG_TAG, "velocityX=" + velocityX + " velocityY=" + velocityY);
            if ((mDragNode != null) && (Math.abs(velocityY) > 1000)) {
                final View view = mDragNode.view;
                final Animation animation = new TranslateAnimation(0, 0, 0,
                        (velocityY > 0 ? 1 : -1) * getMeasuredHeight());

                animation.setDuration(DURATION);
                animation.setInterpolator(getContext(), android.R.anim.accelerate_interpolator);
                animation.setAnimationListener(new AbstractAnimationListener() {
                    @Override
                    public void onAnimationEnd(Animation animation) {
                        mDragNode = null;

                        view.setAnimation(null);
                        removeView(view);
                    }
                });

                view.startAnimation(animation);
                return true;
            }

            if ((gestureListener != null) && gestureListener.onFling(e1, e2, velocityX, velocityY)) {
                return true;
            }
            return false;
        }

        @Override
        public void onLongPress(MotionEvent ev) {
            if (!mEditMode) {
                final View child = findIntersectChild(ev.getX(), ev.getY());
                if (child != null) {
                    setEditMode(true);
                }
            }
            if (gestureListener != null) {
                gestureListener.onLongPress(ev);
            }
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if ((gestureListener != null) && gestureListener.onScroll(e1, e2, distanceX, distanceY)) {
                return true;
            }
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {
            if (gestureListener != null) {
                gestureListener.onShowPress(e);
            }
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            if ((gestureListener != null) && gestureListener.onSingleTapUp(e)) {
                return true;
            }
            return false;
        }
    }

    private class HierarchyChangeListenerImpl implements OnHierarchyChangeListener, OnLongClickListener {
        private OnHierarchyChangeListener listener;

        @Override
        public void onChildViewAdded(View parent, View child) {
            // generates an id if it's missing
            if (child.getId() == View.NO_ID) {
                child.setId(child.hashCode());
            }

            if (child != mRootView) {
                child.setOnLongClickListener(this);
            }
            if (listener != null) {
                listener.onChildViewAdded(parent, child);
            }
        }

        @Override
        public void onChildViewRemoved(View parent, View child) {
            child.setOnLongClickListener(null);
            child.setOnTouchListener(null);

            if ((mRemoveListener != null) && (child != mRootView)) {
                mRemoveListener.onRemove(child, DragGridLayout.this);
            }

            if (listener != null) {
                listener.onChildViewRemoved(parent, child);
            }
        }

        @Override
        public boolean onLongClick(View child) {
            if ((child != null) && (child != mRootView) && !mEditMode) {
                setEditMode(true);
                return true;
            }
            return false;
        }
    }

    public static class LayoutParams extends MarginLayoutParams {
        int mX = UNKNOWN;
        int mY = UNKNOWN;

        int mVerticalSize = 1;
        int mHorizontalSize = 1;

        public LayoutParams() {
            super(MATCH_PARENT, MATCH_PARENT);
        }

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.DragGridLayout);

            mVerticalSize = a.getInt(R.styleable.DragGridLayout_vertical_size, 1);
            mHorizontalSize = a.getInt(R.styleable.DragGridLayout_horizontal_size, 1);

            a.recycle();
        }

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

        LayoutParams(int x, int y, int width, int height) {
            super(width, height);

            this.mX = x;
            this.mY = y;
        }

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

            mX = source.mX;
            mY = source.mY;
            mVerticalSize = source.mVerticalSize;
            mHorizontalSize = source.mHorizontalSize;
        }

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

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

        public Point getPosition() {
            return new Point(mX, mY);
        }

        public LayoutParams setPosition(Point point) {
            mX = point.x;
            mY = point.y;
            return this;
        }

        public LayoutParams setPosition(int pX, int pY) {
            mX = pX;
            mY = pY;
            return this;
        }

        public LayoutParams setHorizontalSize(int horizontalSize) {
            mHorizontalSize = horizontalSize;
            return this;
        }

        public int getHorizontalSize() {
            return mHorizontalSize;
        }

        public LayoutParams setVerticalSize(int verticalSize) {
            mVerticalSize = verticalSize;
            return this;
        }

        public int getVerticalSize() {
            return mHorizontalSize;
        }

        @Override
        public String toString() {
            return String.format(Locale.ENGLISH, "[x=%d;y=%d] [h=%d;v=%d]", mX, mY, mHorizontalSize, mVerticalSize);
        }
    }

    static class Node {
        public static void scale(Rect rect, float scale) {
            final float dw = rect.width() * (1 - scale) * 0.5f;
            final float dh = rect.height() * (1 - scale) * 0.5f;

            rect.left += dw;
            rect.top += dh;
            rect.right -= dw;
            rect.bottom -= dh;
        }

        Rect startRect = new Rect();
        Rect currentRect = new Rect();
        View view;

        BitmapDrawable viewDrawable;

        Node(View view, Rect rect) {
            this.view = view;
            this.viewDrawable = DragGridLayout.createDrawingCache(view);

            startRect.set(rect);
            currentRect.set(rect);
        }

        public void dispose() {
            if (viewDrawable != null) {
                viewDrawable.getBitmap().recycle();
                viewDrawable = null;
            }
        }

        @Override
        public int hashCode() {
            return view.hashCode();
        }
    }

    static abstract class AbstractAnimationListener implements AnimationListener {

        @Override
        public void onAnimationStart(Animation animation) {
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }

    }

    public interface OnCellClickListener {
        void onClick(Point point, DragGridLayout parent);
    }

    public interface OnViewDragListener {
        void onDrag(View view, DragGridLayout parent);

        void onDrop(View view, DragGridLayout parent);
    }

    public interface OnViewRemoveListener {
        void onRemove(View view, DragGridLayout parent);
    }

    private static final int LONGPRESS_MESSAGE = 1;

    private static final int LONGHOVER_MESSAGE = 2;

    private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();

    private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();

    private static final int UNKNOWN = -1;

    private static final int DELTA = 5;

    private static final int DURATION = 200;

    private static final int DEFAULT_CELL_COUNT = 4;

    private static final int DEFAULT_GRAVITY = Gravity.CENTER;

    private static final float SCALE_FACTOR = 0.8f;

    @SuppressWarnings("deprecation")
    private static BitmapDrawable createDrawingCache(View view) {
        BitmapDrawable drawable = null;
        view.buildDrawingCache();
        final Bitmap bitmap = view.getDrawingCache();
        if (bitmap != null) {
            drawable = new BitmapDrawable(Bitmap.createBitmap(bitmap));
            bitmap.recycle();
        }
        view.destroyDrawingCache();
        return drawable;
    }

    private boolean mEditMode;

    private View mRootView;

    private final Set<Cell> mHoveredCells = new HashSet<Cell>();

    private final List<Cell> mCells = new ArrayList<Cell>();

    private int mCellSize;

    private GestureListenerImpl mGestureListener;

    private GestureDetectorCompat mGestureDetector;

    private final Region mCellsRegion = new Region();

    private final Region mFreeCellsRegion = new Region();

    private final Region mTmpRegion = new Region();

    private final Rect mTmpRect = new Rect();

    private Drawable mHighlightDrawable;

    private Drawable mCellDrawable;

    private float mPrevX;

    private float mPrevY;

    private Node mDragNode;

    private OnViewDragListener mDragListener;

    private float mScaleFactor = SCALE_FACTOR;

    private boolean mDebugMode;

    private HierarchyChangeListenerImpl mHierarchyChangeListener;
    private BitmapDrawable mRootViewDrawable;

    private OnCellClickListener mCellClickListener;

    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    @SuppressLint("HandlerLeak")
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case LONGHOVER_MESSAGE:
                requestReorder();
                mLoongHoveredRequested = false;
                break;
            case LONGPRESS_MESSAGE:
                // TODO
                break;
            }
            super.handleMessage(msg);
        }
    };

    private boolean mWidgetVisibility = true;

    private Cell mPressedCell;

    private boolean mEditModeSwitchOff;

    private int mRootViewId = UNKNOWN;

    private OnViewRemoveListener mRemoveListener;

    private boolean mLoongHoveredRequested;

    private int mCellCount = DEFAULT_CELL_COUNT;

    private int mGravity = DEFAULT_GRAVITY;

    private final Set<Node> mNodes = new HashSet<Node>();

    public DragGridLayout(Context context) {
        super(context);

        init();
    }

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

    public DragGridLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DragGridLayout, defStyle, 0);

        mDebugMode = a.getBoolean(R.styleable.DragGridLayout_debug_mode, mDebugMode);
        mCellDrawable = a.getDrawable(R.styleable.DragGridLayout_cell_drawable);
        mHighlightDrawable = a.getDrawable(R.styleable.DragGridLayout_highlight_drawable);
        setCellCount(a.getInteger(R.styleable.DragGridLayout_cell_count, DEFAULT_CELL_COUNT));
        mGravity = a.getInteger(R.styleable.DragGridLayout_android_gravity, DEFAULT_GRAVITY);

        final int rootViewRes = a.getResourceId(R.styleable.DragGridLayout_root_layout, UNKNOWN);
        if (rootViewRes != UNKNOWN) {
            String resTypeName = getResources().getResourceTypeName(rootViewRes);
            if ("layout".equals(resTypeName)) {
                setRootViewRes(rootViewRes);
            } else if ("id".equals(resTypeName)) {
                mRootViewId = rootViewRes;
            }
        }

        a.recycle();

        init();
    }

    @Override
    public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
        if ((mRootViewId != UNKNOWN) && (child.getId() == mRootViewId)) {
            if (mRootView == child) {
                return;
            }

            if (mRootView != null) {
                mRootView.setAnimation(null);
                removeView(mRootView);
            }
            mRootView = child;
            index = 0;
        }

        final LayoutParams lp = validateLayoutParams(params);
        if (mRootView == child) {
            lp.mX = 0;
            lp.mY = 0;
        }
        super.addView(child, index, lp);
    }

    // Override to allow type-checking of LayoutParams.
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return (p != null) && (p instanceof DragGridLayout.LayoutParams);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (mDebugMode) {
            mPaint.setStyle(Style.FILL_AND_STROKE);
            final Path path = new Path();

            path.reset();
            mCellsRegion.getBoundaryPath(path);
            path.close();
            mPaint.setColor(0x660000cc);
            canvas.drawPath(path, mPaint);

            path.reset();
            mFreeCellsRegion.getBoundaryPath(path);
            path.close();
            mPaint.setColor(0x66cc0000);
            canvas.drawPath(path, mPaint);
        }

        if (mRootView == null) {
            drawCellGrid(canvas);
        }
        super.dispatchDraw(canvas);
    }

    private void drawCellGrid(Canvas canvas) {
        if (mCellDrawable != null) {
            int i = 0;
            for (final Cell cell : mCells) {
                mTmpRect.set(cell.rect);

                final int[] stateSet = new int[] { (mEditMode ? 1 : -1) * R.attr.state_editing,
                        (mPressedCell == cell ? 1 : -1) * android.R.attr.state_pressed };

                canvas.save();
                canvas.clipRect(cell.rect);
                mCellDrawable.setState(stateSet);
                mCellDrawable.setBounds(cell.rect);
                mCellDrawable.draw(canvas);

                if (mDebugMode) {
                    mPaint.setTextAlign(Align.CENTER);
                    mPaint.setTextSize(30);
                    mPaint.setTypeface(Typeface.DEFAULT_BOLD);
                    mPaint.setColor(Color.GREEN);
                    canvas.drawText(Integer.toString(i), cell.rect.centerX(), cell.rect.centerY(), mPaint);
                    ++i;
                }

                canvas.restore();
            }
        }

        if (mDebugMode) {
            mPaint.setColor(Color.GREEN);
            mPaint.setStyle(Style.STROKE);
            mPaint.setStrokeWidth(1.5f);
            canvas.drawPath(mCellsRegion.getBoundaryPath(), mPaint);
        }
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        if (child == mRootView) {
            final boolean result = drawChildDrawable(mRootViewDrawable, canvas, child, drawingTime);
            drawCellGrid(canvas);
            return result;
        } else if (mWidgetVisibility) {
            if ((mDragNode != null) && (mDragNode.view == child)) {
                drawHighlight(child, canvas);
                if (mDragNode.view.getAnimation() != null) {
                    return super.drawChild(canvas, child, drawingTime);
                }

                if ((mDragNode != null) && (mDragNode.viewDrawable == null)) {
                    mDragNode.viewDrawable = DragGridLayout.createDrawingCache(mDragNode.view);
                }
                return drawChildDrawable(mDragNode != null ? mDragNode.viewDrawable : null, canvas, child,
                        drawingTime);
            } else {
                return super.drawChild(canvas, child, drawingTime);
            }
        }
        return false;
    }

    private boolean drawChildDrawable(BitmapDrawable childDrawable, Canvas canvas, View child, long drawingTime) {
        canvas.save();
        requestCurrentRect(mTmpRect, child);
        if (child.getAnimation() == null) {
            canvas.clipRect(mTmpRect);
        }

        final boolean result;
        if (mEditMode && (childDrawable != null) && !childDrawable.getBitmap().isRecycled()) {
            childDrawable.setBounds(mTmpRect);
            childDrawable.draw(canvas);
            result = false;
        } else {
            result = super.drawChild(canvas, child, drawingTime);
        }
        canvas.restore();
        return result;
    }

    private void drawHighlight(final View child, final Canvas canvas) {
        if ((mHighlightDrawable != null)) {
            requestFreeCellRegion(mDragNode.view);
            if (!requestHoverRect(mTmpRect)) {
                return;
            }
            canvas.save();
            canvas.clipRect(mTmpRect);
            mHighlightDrawable.setBounds(mTmpRect);

            mTmpRegion.set(mFreeCellsRegion);
            mTmpRegion.op(mTmpRect, Op.INTERSECT);
            mTmpRegion.getBounds(mTmpRect);

            final boolean allowed = mTmpRegion.isRect() && (child.getWidth() <= mTmpRect.width())
                    && (child.getHeight() <= mTmpRect.height());
            final int[] stateSet = new int[] { (allowed ? 1 : -1) * R.attr.state_drop_allow };

            mHighlightDrawable.setState(stateSet);
            mHighlightDrawable.draw(canvas);
            canvas.restore();
        }
    }

    private void fillHoveredCells(Cell startCell, int vDir, int hDir, int vSize, int hSize) {
        Cell rowCell = startCell;
        for (int i = 0; i < vSize; ++i) {
            Cell colCell = rowCell;
            for (int j = 0; j < hSize; ++j) {
                mHoveredCells.add(colCell);
                colCell = (hDir > 0) ? rowCell.right : rowCell.left;
                if (colCell == null) {
                    break;
                }
            }
            rowCell = (vDir > 0) ? rowCell.bottom : rowCell.top;
            if (rowCell == null) {
                break;
            }
        }
    }

    private Cell findCellUnder(float x, float y) {
        final Rect rect = new Rect((int) x, (int) y, Math.round(x), Math.round(y));
        return findCellUnder(rect);
    }

    private Cell findCellUnder(Rect rect) {
        Cell resCell = null;
        final Rect resRect = new Rect();
        for (final Cell cell : mCells) {
            if (mTmpRect.setIntersect(cell.rect, rect)) {
                if ((resCell == null)
                        || ((mTmpRect.width() * mTmpRect.height()) > (resRect.width() * resRect.height()))) {
                    resRect.set(mTmpRect);
                    resCell = cell;
                }
            }
        }

        return resCell;
    }

    public Cell findFreeCell(int rows, int cols, Region freeRegion) {
        if (mCells.isEmpty()) {
            return null;
        }

        final Rect boundRect = new Rect();
        final Rect rect = new Rect(0, 0, cols * mCellSize, rows * mCellSize);

        for (final Cell cell : mCells) {
            mTmpRegion.set(freeRegion);
            rect.offsetTo(cell.rect.left, cell.rect.top);

            mTmpRegion.op(rect, Op.INTERSECT);
            if (!mTmpRegion.isEmpty() && mTmpRegion.isRect()) {
                mTmpRegion.getBounds(boundRect);
                if ((boundRect.width() == rect.width()) && (boundRect.height() == rect.height())) {
                    return cell;
                }
            }
        }

        return null;
    }

    private View findIntersectChild(float x, float y) {
        final int count = getChildCount();
        for (int i = count - 1; i >= 0; --i) {
            final View child = getChildAt(i);
            if (child == mRootView) {
                continue;
            }

            requestCurrentRect(mTmpRect, child);
            if (mTmpRect.contains((int) x, (int) y)) {
                return child;
            }
        }
        return null;
    }

    private Set<Node> findNodesUnder(Node dragNode, Set<Cell> hoveredCells) {
        if ((dragNode == null) || hoveredCells.isEmpty()) {
            return Collections.emptySet();
        }

        final Rect tmpRect = new Rect();
        final Region tmpRegion = new Region();
        for (Cell cell : hoveredCells) {
            tmpRegion.union(cell.rect);
        }

        Set<Node> nodes = new HashSet<Node>();
        int childCount = getChildCount();
        for (int i = 0; i < childCount; ++i) {
            View child = getChildAt(i);
            if ((child == mRootView) || (child == dragNode.view)) {
                continue;
            }

            requestCurrentRect(tmpRect, child);
            mTmpRegion.set(tmpRegion);
            mTmpRegion.op(tmpRect, Op.INTERSECT);

            if (!mTmpRegion.isEmpty()) {
                nodes.add(new Node(child, tmpRect));
            }
        }
        return nodes;
    }

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

    @Override
    protected DragGridLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        if (p == null) {
            return new DragGridLayout.LayoutParams();
        } else if (p instanceof DragGridLayout.LayoutParams) {
            return new DragGridLayout.LayoutParams((DragGridLayout.LayoutParams) p);
        } else if (p instanceof MarginLayoutParams) {
            return new DragGridLayout.LayoutParams((MarginLayoutParams) p);
        }
        return new DragGridLayout.LayoutParams(p);
    }

    private int getArea(int l, int t, int r, int b) {
        int w = r - l;
        int h = b - t;

        return (w * w) + (h * h);
    }

    public List<Cell> getCells() {
        return mCells;
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
        if ((mRootView != null) && (indexOfChild(mRootView) != 0)) {
            throw new IllegalStateException("Root vie)w index shoudl be 0");
        }
        if (mDragNode != null) {
            final int index = indexOfChild(mDragNode.view);
            if (i < index) {
                return i;
            } else if (i < (childCount - 1)) {
                return i + 1;
            } else {
                return index;
            }
        }
        return i;
    }

    public Drawable getHighlightDrawable() {
        return mHighlightDrawable;
    }

    @Override
    public View getRootView() {
        return mRootView;
    }

    private void init() {
        mHierarchyChangeListener = new HierarchyChangeListenerImpl();
        mGestureListener = new GestureListenerImpl();
        mGestureDetector = new GestureDetectorCompat(getContext(), mGestureListener);

        mGestureDetector.setIsLongpressEnabled(true);
        setLongClickable(true);
        super.setOnHierarchyChangeListener(mHierarchyChangeListener);
        setChildrenDrawingOrderEnabled(true);
        setClipToPadding(false);
    }

    public boolean isDebugMode() {
        return mDebugMode;
    }

    public boolean isEditMode() {
        return mEditMode;
    }

    public boolean isShowWidget() {
        return mWidgetVisibility;
    }

    private boolean isSpaceFree(LayoutParams lp, Region freeRagion) {
        if (mCells.isEmpty() || (lp.mX == UNKNOWN) || (lp.mY == UNKNOWN)) {
            return false;
        }

        final Rect rect = new Rect(lp.leftMargin, lp.topMargin, (lp.mHorizontalSize * mCellSize) - lp.rightMargin,
                (lp.mVerticalSize * mCellSize) - lp.bottomMargin);
        rect.offset(lp.mX, lp.mY);

        mTmpRegion.set(freeRagion);
        mTmpRegion.op(rect, Op.INTERSECT);
        if (!mTmpRegion.isEmpty() && mTmpRegion.isRect()) {
            mTmpRegion.getBounds(mTmpRect);
            return ((mTmpRect.width() == rect.width()) && (mTmpRect.height() == rect.height()));
        }
        return false;
    }

    @Override
    protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
        final int childWidthMeasureSpec;
        final int childHeightMeasureSpec;
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        if (mRootView == child) {
            childWidthMeasureSpec = parentWidthMeasureSpec;
            childHeightMeasureSpec = parentHeightMeasureSpec;
        } else if (((mDragNode) != null) && (mDragNode.view == child)) {
            final int width = mDragNode.currentRect.width();
            final int height = mDragNode.currentRect.height();

            final int widthSize = MeasureSpec.getSize(parentWidthMeasureSpec);
            final int heightSize = MeasureSpec.getSize(parentHeightMeasureSpec);

            childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(
                    MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
                    getPaddingLeft() + getPaddingRight(), width);
            childHeightMeasureSpec = ViewGroup.getChildMeasureSpec(
                    MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY),
                    getPaddingTop() + getPaddingBottom(), height);
        } else {
            final int width = (lp.mHorizontalSize * mCellSize) - (lp.leftMargin + lp.rightMargin);
            final int height = (lp.mVerticalSize * mCellSize) - (lp.topMargin + lp.bottomMargin);

            childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(parentWidthMeasureSpec,
                    getPaddingLeft() + getPaddingRight(), width);
            childHeightMeasureSpec = ViewGroup.getChildMeasureSpec(parentHeightMeasureSpec,
                    getPaddingTop() + getPaddingBottom(), height);
        }
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    @Override
    protected void onDetachedFromWindow() {
        if (mRootViewDrawable != null) {
            mRootViewDrawable.getBitmap().recycle();
            mRootViewDrawable = null;
        }
        mLoongHoveredRequested = false;
        mHandler.removeMessages(LONGPRESS_MESSAGE);
        mHandler.removeMessages(LONGHOVER_MESSAGE);
        super.onDetachedFromWindow();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mEditMode) {
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }

            final int childLeft;
            final int childTop;
            final int childRight;
            final int childBottom;

            final DragGridLayout.LayoutParams lp = (DragGridLayout.LayoutParams) child.getLayoutParams();

            if ((mDragNode != null) && (mDragNode.view == child)) {
                childLeft = mDragNode.currentRect.left;
                childTop = mDragNode.currentRect.top;
                childRight = mDragNode.currentRect.right;
                childBottom = mDragNode.currentRect.bottom;
            } else if (child == mRootView) {
                childLeft = 0;
                childTop = 0;
                childRight = r - l;
                childBottom = b - t;
            } else if ((lp.mX != UNKNOWN) && (lp.mY != UNKNOWN)) {
                childLeft = lp.mX + lp.leftMargin;
                childTop = lp.mY + lp.topMargin;
                childRight = childLeft + child.getMeasuredWidth();
                childBottom = childTop + child.getMeasuredHeight();
            } else {
                childLeft = 0;
                childTop = 0;
                childRight = 0;
                childBottom = 0;
            }

            child.layout(childLeft, childTop, childRight, childBottom);
        }
    }

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

        //resolveGrid(getMeasuredWidth(), getMeasuredHeight());

        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        resolveGrid(w, h);
    }

    private void resolveGrid(int w, int h) {
        mCellsRegion.setEmpty();

        mCells.clear();
        mHoveredCells.clear();
        mCellSize = resolveCellSize(w, h);
        if (!(mCellSize > 0)) {
            return;
        }

        int cellCount = (w - (getPaddingLeft() + getPaddingRight())) / mCellSize;
        int x = getPaddingLeft() + resolveOffset(w - (getPaddingLeft() + getPaddingRight()), mCellSize, cellCount,
                mGravity & Gravity.HORIZONTAL_GRAVITY_MASK);
        int vCcellCount = (h - (getPaddingTop() + getPaddingBottom())) / mCellSize;
        int y = getPaddingTop() + resolveOffset(h - (getPaddingTop() + getPaddingBottom()), mCellSize, vCcellCount,
                (mGravity & Gravity.VERTICAL_GRAVITY_MASK));

        final Cell[] prevRow = new Cell[cellCount];
        final Cell[] currRow = new Cell[cellCount];

        while ((y + mCellSize) < h) {
            resolveCell(currRow, prevRow, x, y, mCells);
            System.arraycopy(currRow, 0, prevRow, 0, currRow.length);
            y += mCellSize;
        }

        mCellsRegion.set(mCells.get(0).rect.left, mCells.get(0).rect.top, mCells.get(mCells.size() - 1).rect.right,
                mCells.get(mCells.size() - 1).rect.bottom);

        if (validateChildrenLayoutParams()) {
            requestLayout();
            invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final float x = ev.getX();
        final float y = ev.getY();
        if (mGestureDetector.onTouchEvent(ev)) {
            mPrevX = x;
            mPrevY = y;
            return true;
        }

        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            if (mEditMode) {
                final View child = findIntersectChild(x, y);
                if (child != null) {
                    if ((mDragNode != null) && (mDragNode.view != child)) {
                        return true;
                    }
                    mPrevX = x;
                    mPrevY = y;

                    stopAnimation(mDragNode != null ? mDragNode.view : null);

                    mDragNode = new Node(child, requestPreferredRect(mTmpRect, child));
                    Node.scale(mDragNode.currentRect, mScaleFactor);

                    requestFreeCellRegion(child);
                    requestHoveredCells(mDragNode);

                    if (mDragListener != null) {
                        mDragListener.onDrag(mDragNode.view, this);
                    }

                    final Animation animation = new ScaleAnimation((1f - mScaleFactor) + 1f, 1f,
                            (1f - mScaleFactor) + 1f, 1f, Animation.RELATIVE_TO_SELF, 0.5f,
                            Animation.RELATIVE_TO_SELF, 0.5f);

                    animation.setDuration(DURATION);
                    animation.setAnimationListener(new AbstractAnimationListener() {
                        @Override
                        public void onAnimationEnd(Animation animation) {
                            mTmpRect.set(mDragNode.startRect);

                            child.setAnimation(null);
                            child.requestLayout();
                            invalidate(mTmpRect);
                        }
                    });

                    requestLayout();
                    child.startAnimation(animation);

                    return true;
                }

                final Cell cell = findCellUnder(x, y);
                if (cell != null) {
                    mPressedCell = cell;
                    invalidate();
                    return true;
                }

                mEditModeSwitchOff = true;
                return true;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            if (mPressedCell != null) {
                final Cell cell = findCellUnder(x, y);
                if (mPressedCell != cell) {
                    mPressedCell = null;
                    invalidate();
                    return true;
                }
            } else if (mDragNode != null) {
                mDragNode.currentRect.offset((int) (x - mPrevX), (int) (y - mPrevY));
                if (mDebugMode) {
                    Log.w(VIEW_LOG_TAG, "ACTION_MOVE: x=" + x + " y=" + y + " prevX=" + mPrevX + " prevY=" + mPrevY
                            + " dX=" + (x - mPrevX) + " dY=" + (y - mPrevY));
                }

                requestHoveredCells(mDragNode);
                boolean dragged = (Math.abs(x - mPrevX) < DELTA) && (Math.abs(y - mPrevY) < DELTA);
                if (dragged) {
                    requestReorderRevert();
                }
                if (!mHoveredCells.isEmpty() && dragged) {
                    if (!mLoongHoveredRequested) {
                        mLoongHoveredRequested = true;
                        mHandler.sendEmptyMessageDelayed(LONGHOVER_MESSAGE, LONGPRESS_TIMEOUT + TAP_TIMEOUT);
                    }
                } else if (mLoongHoveredRequested) {
                    mLoongHoveredRequested = false;
                    mHandler.removeMessages(LONGHOVER_MESSAGE);
                }

                mPrevX = x;
                mPrevY = y;

                requestLayout();
                invalidate();
                return true;
            }
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            mLoongHoveredRequested = false;
            mHandler.removeMessages(LONGHOVER_MESSAGE);
            if (mPressedCell != null) {
                if (mCellClickListener != null) {
                    mCellClickListener.onClick(new Point(mPressedCell.rect.left, mPressedCell.rect.top), this);
                }
                mPressedCell = null;
                invalidate();
                return true;
            } else if (mDragNode != null) {
                mDragNode.currentRect.offset((int) (x - mPrevX), (int) (y - mPrevY));

                requestHoveredCells(mDragNode);
                requestReorderRevert();
                requestDrop(mDragNode);

                mPrevX = x;
                mPrevY = y;

                mTmpRect.set(mDragNode.currentRect);
                mTmpRect.union(mDragNode.startRect);

                final View child = mDragNode.view;
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                lp.mX = mDragNode.startRect.left;
                lp.mY = mDragNode.startRect.top;

                mNodes.clear();
                mDragNode.startRect.offset(lp.leftMargin, lp.topMargin);

                final AnimationSet animation = new AnimationSet(true);
                animation.addAnimation(new TranslateAnimation(mDragNode.currentRect.left - mDragNode.startRect.left,
                        0, mDragNode.currentRect.top - mDragNode.startRect.top, 0));
                animation.addAnimation(new ScaleAnimation(mScaleFactor, 1f, mScaleFactor, 1f));

                animation.setDuration(DURATION);
                animation.setAnimationListener(new AbstractAnimationListener() {
                    @Override
                    public void onAnimationEnd(final Animation a) {
                        mDragNode.dispose();
                        mDragNode = null;

                        child.setAnimation(null);

                        requestLayout();
                        invalidate();
                    }
                });

                if (mDragListener != null) {
                    mDragListener.onDrop(mDragNode.view, this);
                }

                mDragNode.currentRect.set(mDragNode.startRect);

                child.requestLayout();
                child.startAnimation(animation);
                invalidate(mTmpRect);
                return true;
            } else if (mEditModeSwitchOff) {
                setEditMode(false);
                mEditModeSwitchOff = false;
                return true;
            }
            break;
        }

        return super.onTouchEvent(ev);
    }

    public void removeRootView() {
        if (mRootView != null) {
            super.removeView(mRootView);
        }
    }

    private Rect requestCurrentRect(Rect outRect, View view) {
        if (outRect == null) {
            outRect = new Rect();
        }

        outRect.left = view.getLeft();
        outRect.top = view.getTop();
        outRect.right = view.getRight();
        outRect.bottom = view.getBottom();

        return outRect;
    }

    private void requestDrop(Node node) {
        requestFreeCellRegion(node.view);
        if (requestHoveredCells(node)) {
            requestHoverRect(mTmpRect);

            mTmpRegion.set(mFreeCellsRegion);
            mTmpRegion.op(mTmpRect, Op.INTERSECT);

            Rect bound = mTmpRegion.getBounds();
            if (!mTmpRegion.isEmpty() && !mTmpRegion.isComplex() && (bound.width() == mTmpRect.width())
                    && (bound.height() == mTmpRect.height())) {
                node.startRect.offsetTo(mTmpRect.left, mTmpRect.top);
                return;
            }
        }

        final LayoutParams lp = (LayoutParams) node.view.getLayoutParams();
        final LayoutParams newLp = validateLayoutParams(generateLayoutParams(lp), mFreeCellsRegion);
        if ((lp.mX == newLp.mX) && (lp.mY == newLp.mY)) {
            return;
        }
        node.startRect.offsetTo(newLp.mX, newLp.mY);
    }

    private void requestFreeCellRegion(View... views) {
        mFreeCellsRegion.set(mCellsRegion);
        final int count = getChildCount();
        for (int i = count - 1; i >= 0; --i) {
            final View child = getChildAt(i);

            if (child == mRootView) {
                continue;
            }
            boolean needContinue = false;

            for (View view : views) {
                if (view == child) {
                    needContinue = true;
                    break;
                }
            }
            if (needContinue) {
                continue;
            }

            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if ((lp.mX == UNKNOWN) || (lp.mY == UNKNOWN)) {
                continue;
            }

            mFreeCellsRegion.op(requestPreferredRect(mTmpRect, child), Op.DIFFERENCE);
        }
    }

    private boolean requestHoveredCells(Node node) {
        mHoveredCells.clear();
        if (mCellsRegion.quickReject(node.currentRect)) {
            return false;
        }

        mTmpRegion.set(mCellsRegion);
        mTmpRegion.op(node.currentRect, Op.INTERSECT);

        final View view = node.view;
        final LayoutParams lp = (LayoutParams) view.getLayoutParams();

        int cellCount = lp.mHorizontalSize * lp.mVerticalSize;

        final Rect rect = mTmpRegion.getBounds();

        Cell ltCell = null;
        Cell rtCell = null;
        Cell lbCell = null;
        Cell rbCell = null;

        mTmpRegion.setEmpty();
        for (final Cell cell : mCells) {
            if (mTmpRect.setIntersect(cell.rect, rect)) {
                if (mTmpRegion.isEmpty()) {
                    mTmpRegion.set(cell.rect);
                    ltCell = rtCell = lbCell = rbCell = cell;
                } else {
                    mTmpRegion.op(cell.rect, Op.UNION);

                    if ((ltCell.rect.left >= cell.rect.left) && (ltCell.rect.top >= cell.rect.top)) {
                        ltCell = cell;
                    }
                    if ((rtCell.rect.right <= cell.rect.right) && (rtCell.rect.top >= cell.rect.top)) {
                        rtCell = cell;
                    }
                    if ((rbCell.rect.bottom <= cell.rect.bottom) && (lbCell.rect.left >= cell.rect.left)) {
                        lbCell = cell;
                    }
                    if ((rbCell.rect.bottom <= cell.rect.bottom) && (rbCell.rect.right <= cell.rect.right)) {
                        rbCell = cell;
                    }
                }
            }
        }

        if ((ltCell == null) || (rtCell == null) || (lbCell == null) || (rbCell == null)) {
            return false;
        }

        mTmpRegion.getBounds(mTmpRect);
        int lt = getArea(mTmpRect.left, mTmpRect.top, rect.left, rect.top);
        int rt = getArea(rect.right, mTmpRect.top, mTmpRect.right, rect.top);
        int lb = getArea(mTmpRect.left, rect.bottom, rect.left, mTmpRect.bottom);
        int rb = getArea(rect.right, rect.bottom, mTmpRect.right, mTmpRect.bottom);

        int min = Math.min(Math.min(lt, rt), Math.min(lb, rb));
        if (min == lt) {
            fillHoveredCells(ltCell, 1, 1, lp.mVerticalSize, lp.mHorizontalSize);
        } else if (min == rt) {
            fillHoveredCells(rtCell, 1, -1, lp.mVerticalSize, lp.mHorizontalSize);
        } else if (min == lb) {
            fillHoveredCells(lbCell, -1, 1, lp.mVerticalSize, lp.mHorizontalSize);
        } else if (min == rb) {
            fillHoveredCells(rbCell, -1, -1, lp.mVerticalSize, lp.mHorizontalSize);
        }

        return cellCount == mHoveredCells.size();
    }

    private boolean requestHoverRect(Rect rect) {
        rect.setEmpty();
        for (final Cell cell : mHoveredCells) {
            if (!rect.isEmpty()) {
                rect.union(cell.rect);
            } else {
                rect.set(cell.rect);
            }
        }
        return !mHoveredCells.isEmpty() && !rect.isEmpty();
    }

    private Rect requestPreferredRect(Rect outRect, View view) {
        if (outRect == null) {
            outRect = new Rect();
        }
        final LayoutParams lp = checkLayoutParams(view.getLayoutParams()) ? (LayoutParams) view.getLayoutParams()
                : generateLayoutParams(view.getLayoutParams());

        outRect.left = lp.mX;
        outRect.top = lp.mY;
        outRect.right = outRect.left + (mCellSize * lp.mHorizontalSize);
        outRect.bottom = outRect.top + (mCellSize * lp.mVerticalSize);

        return outRect;
    }

    private void requestReorder() {
        if (mDragNode == null) {
            return;
        }
        Set<Node> nodes = findNodesUnder(mDragNode, mHoveredCells);

        for (final Node childNode : nodes) {
            requestFreeCellRegion(childNode.view, mDragNode.view);
            mFreeCellsRegion.op(mDragNode.currentRect, Op.DIFFERENCE);

            final LayoutParams lp = (LayoutParams) childNode.view.getLayoutParams();
            final LayoutParams newLp = validateLayoutParams(generateLayoutParams(lp), mFreeCellsRegion);
            if ((lp.mX == newLp.mX) && (lp.mY == newLp.mY)) {
                continue;
            }
            childNode.view.setLayoutParams(newLp);

            requestPreferredRect(childNode.currentRect, childNode.view);

            final AnimationSet animation = new AnimationSet(true);
            animation.addAnimation(new TranslateAnimation(childNode.startRect.left - childNode.currentRect.left, 0,
                    childNode.startRect.top - childNode.currentRect.top, 0));

            animation.setDuration(DURATION / 2);
            animation.setAnimationListener(new AbstractAnimationListener() {
                @Override
                public void onAnimationEnd(final Animation a) {
                    childNode.view.setAnimation(null);

                    requestLayout();
                    invalidate();
                }
            });
            childNode.view.setAnimation(animation);
            mNodes.add(childNode);
        }

        if (!nodes.isEmpty()) {
            requestFreeCellRegion();
            requestLayout();
            invalidate();
        }
    }

    private void requestReorderRevert() {
        boolean needInvalidate = false;
        for (final Iterator<Node> it = mNodes.iterator(); it.hasNext();) {
            final Node node = it.next();
            if (mDragNode != null) {
                requestFreeCellRegion(mDragNode.view);
            } else {
                requestFreeCellRegion();
            }
            for (Cell cell : mHoveredCells) {
                mFreeCellsRegion.op(cell.rect, Op.DIFFERENCE);
            }

            mTmpRegion.set(mFreeCellsRegion);
            mTmpRegion.op(node.startRect, Op.INTERSECT);
            mTmpRegion.getBounds(mTmpRect);
            if ((mTmpRect.width() == node.startRect.width()) && (mTmpRect.height() == node.startRect.height())) {
                it.remove();

                LayoutParams lp = (LayoutParams) node.view.getLayoutParams();
                lp.mX = node.startRect.left - lp.leftMargin;
                lp.mY = node.startRect.top - lp.topMargin;

                final AnimationSet animation = new AnimationSet(true);
                animation.addAnimation(new TranslateAnimation(-node.startRect.left + node.currentRect.left, 0,
                        -node.startRect.top + node.currentRect.top, 0));

                animation.setDuration(DURATION / 2);
                animation.setAnimationListener(new AbstractAnimationListener() {
                    @Override
                    public void onAnimationEnd(final Animation a) {
                        node.view.setAnimation(null);

                        requestLayout();
                        invalidate();
                    }
                });
                node.view.setAnimation(animation);
                needInvalidate = true;
            }
        }

        if (needInvalidate) {
            invalidate();
        }
    }

    private void resolveCell(Cell[] curr, Cell[] prev, int offsetX, int offsetY, List<Cell> cells) {
        for (int i = 0; i < curr.length; ++i) {
            curr[i] = new Cell(offsetX + (i * mCellSize), offsetY, mCellSize);

            if (((i - 1) != -1) && (curr[i - 1] != null)) {
                curr[i - 1].right = curr[i];
            }

            curr[i].left = (i - 1) != -1 ? curr[i - 1] : null;
            curr[i].top = (prev[i]);
            if (prev[i] != null) {
                prev[i].bottom = curr[i];
            }

            cells.add(curr[i]);
        }
    }

    private int resolveCellSize(int w, int h) {
        w = Math.max(0, w - (getPaddingLeft() + getPaddingRight()));
        h = Math.max(0, h - (getPaddingTop() + getPaddingBottom()));

        final int orientation = getResources().getConfiguration().orientation;
        switch (orientation) {
        case Configuration.ORIENTATION_LANDSCAPE:
            return h / getCellCount();
        case Configuration.ORIENTATION_PORTRAIT:
            return w / getCellCount();
        default:
            return Math.max(h, w) / getCellCount();
        }
    }

    private int resolveOffset(int size, int cellSize, int cellCount, int gravity) {
        switch (gravity) {
        case Gravity.LEFT:
        case Gravity.TOP:
            return 0;
        case Gravity.RIGHT:
        case Gravity.BOTTOM:
            return Math.max(0, size - (cellCount * cellSize));
        case Gravity.CENTER_HORIZONTAL:
        case Gravity.CENTER_VERTICAL:
        default:
            return Math.max(0, (size - (cellCount * cellSize)) / 2);
        }
    }

    public void setCellClickListener(OnCellClickListener cellClickListener) {
        this.mCellClickListener = cellClickListener;
    }

    public void setCellImage(Drawable cellDrawable) {
        this.mCellDrawable = cellDrawable;
    }

    public void setCellImage(int cellRes) {
        setCellImage(getResources().getDrawable(cellRes));
    }

    public void setDebugMode(boolean debugMode) {
        if (this.mDebugMode != debugMode) {
            this.mDebugMode = debugMode;
            invalidate();
        }
    }

    public void setDragListener(OnViewDragListener dragListener) {
        this.mDragListener = dragListener;
    }

    public void setDragScaleCoefficient(float scale) {
        this.mScaleFactor = scale;
    }

    public void setEditMode(boolean value) {
        if (this.mEditMode == value) {
            return;
        }

        mEditMode = value;
        if (mRootView != null) {
            if (mEditMode) {
                mRootViewDrawable = DragGridLayout.createDrawingCache(mRootView);
            } else if (mRootViewDrawable != null) {
                mRootViewDrawable.getBitmap().recycle();
                mRootViewDrawable = null;
            }
        }
        postInvalidate();
    }

    public void setGestureListener(OnGestureListener listener) {
        mGestureListener.gestureListener = listener;
    }

    public void setHighlight(Drawable highlightDrawable) {
        this.mHighlightDrawable = highlightDrawable;
    }

    public void setHighlight(int highlightRes) {
        setHighlight(getResources().getDrawable(highlightRes));
    }

    @Override
    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
        this.mHierarchyChangeListener.listener = listener;
    }

    public void setRemoveListener(OnViewRemoveListener removeListener) {
        this.mRemoveListener = removeListener;
    }

    public void setRootView(View view) {
        if (view != mRootView) {
            removeRootView();
        }
        mRootView = view;
        addView(mRootView, 0, new LayoutParams(0, 0, android.view.ViewGroup.LayoutParams.MATCH_PARENT,
                android.view.ViewGroup.LayoutParams.MATCH_PARENT));
    }

    public void setRootViewRes(int viewId) {
        setRootView(View.inflate(getContext(), viewId, null));
    }

    public void setShowWidget(boolean showWidget) {
        if (this.mWidgetVisibility != showWidget) {
            this.mWidgetVisibility = showWidget;
            invalidate();
        }
    }

    private void stopAnimation(View view) {
        if ((view != null) && (view.getAnimation() != null)) {
            view.getAnimation().cancel();
            view.setAnimation(null);
            invalidate();
        }
    }

    private boolean validateChildrenLayoutParams() {
        boolean needRequestLayout = false;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; ++i) {
            View child = getChildAt(i);

            if (child == mRootView) {
                continue;
            }

            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if ((lp.mX == UNKNOWN) || (lp.mY == UNKNOWN)) {
                child.setLayoutParams(validateLayoutParams(lp));
                needRequestLayout = true;
            }
        }
        return needRequestLayout;
    }

    public LayoutParams validateLayoutParams(ViewGroup.LayoutParams srcLp) {
        requestFreeCellRegion();
        return validateLayoutParams(srcLp, mFreeCellsRegion);
    }

    public LayoutParams validateLayoutParams(ViewGroup.LayoutParams srcLp, Region freeRehion) {
        final LayoutParams lp;
        if (checkLayoutParams(srcLp)) {
            lp = (LayoutParams) srcLp;
        } else {
            lp = generateLayoutParams(srcLp);
        }

        if (mCells.isEmpty()) {
            return lp;
        }

        if (isSpaceFree(lp, freeRehion)) {
            return lp;
        }

        final Cell cell = findFreeCell(lp.mVerticalSize, lp.mHorizontalSize, freeRehion);
        if (cell != null) {
            lp.mX = cell.rect.left;
            lp.mY = cell.rect.top;
        }
        return lp;
    }

    public int getCellCount() {
        return mCellCount;
    }

    public void setCellCount(int cellCount) {
        if (mCellCount != cellCount) {
            mCellCount = cellCount;
            requestLayout();
        }
    }
}