/* * Catroid: An on-device visual programming system for Android devices * Copyright (C) 2010-2016 The Catrobat Team * (<http://developer.catrobat.org/credits>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * An additional term exception under section 7 of the GNU Affero * General Public License, version 3, is available at * http://developer.catrobat.org/license_additional_term * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catrobat.catroid.ui.dragndrop; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.View.OnLongClickListener; import android.view.WindowManager; import android.widget.ImageView; import android.widget.ListView; import org.catrobat.catroid.R; import org.catrobat.catroid.common.ScreenValues; import org.catrobat.catroid.ui.adapter.BrickAdapter; import org.catrobat.catroid.utils.Utils; public class BrickDragAndDropListView extends ListView implements OnLongClickListener { private static final int SCROLL_SPEED = 25; public static final int WIDTH_OF_BRICK_PREVIEW_IMAGE = 400; private static final int DRAG_BACKGROUND_COLOR = Color.TRANSPARENT; private int maximumDragViewHeight; private int previousItemPosition; private int touchPointY; private int upperScrollBound; private int lowerScrollBound; private int upperDragBound; private int lowerDragBound; private ImageView dragView; private int position; private boolean newView; private int touchedListPosition; private boolean dimBackground; private boolean dragNewBrick; private boolean isScrolling; private long blinkAnimationTimestamp; private DragAndDropListener dragAndDropListener; public BrickDragAndDropListView(Context context) { super(context); } public BrickDragAndDropListView(Context context, AttributeSet attributes) { super(context, attributes); } public BrickDragAndDropListView(Context context, AttributeSet attributes, int defStyle) { super(context, attributes, defStyle); } public void setOnDragAndDropListener(DragAndDropListener listener) { dragAndDropListener = listener; } public void setInsertedBrick(int pos) { this.position = pos; newView = true; } @Override public void draw(Canvas canvas) { super.draw(canvas); if (dimBackground) { Rect rect = new Rect(0, 0, canvas.getWidth(), canvas.getHeight()); Paint paint = new Paint(); paint.setColor(Color.BLACK); paint.setStyle(Style.FILL); paint.setAlpha(128); canvas.drawRect(rect, paint); } } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); if (y < 0) { y = 0; } else if (y > getHeight()) { y = getHeight(); } int itemPosition = pointToPosition(x, y); itemPosition = itemPosition < 0 ? ((BrickAdapter) dragAndDropListener).getCount() - 1 : itemPosition; if (touchedListPosition != itemPosition) { touchedListPosition = itemPosition; if (dragAndDropListener != null) { dragAndDropListener.setTouchedScript(touchedListPosition); } } if (dragAndDropListener != null && dragView != null) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: setDragViewAnimation(0); dragAndDropListener.drop(); stopDragging(); dimBackground = false; dragNewBrick = false; break; case MotionEvent.ACTION_MOVE: scrollListWithDraggedItem(y); dragTouchedListItem((int) event.getRawY()); dragItemInList(y, itemPosition); dimBackground = true; break; } return true; } return super.onTouchEvent(event); } @Override protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { super.onSizeChanged(width, height, oldWidth, oldHeight); upperScrollBound = height / 6; lowerScrollBound = height * 5 / 6; maximumDragViewHeight = height / 2; } public static Bitmap getBitmapFromView(View view, int width, int height) { view.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.translate(-view.getScrollX(), -view.getScrollY()); view.draw(canvas); return bitmap; } @Override public boolean onLongClick(View view) { if (((BrickAdapter) getAdapter()).getActionMode() != BrickAdapter.ActionModeEnum.NO_ACTION) { return true; } ((BrickAdapter) getAdapter()).isDragging = true; ((BrickAdapter) getAdapter()).setSpinnersEnabled(false); int itemPosition = calculateItemPositionAndTouchPointY(view); boolean drawingCacheEnabled = view.isDrawingCacheEnabled(); view.setDrawingCacheEnabled(true); view.measure(MeasureSpec.makeMeasureSpec(ScreenValues.SCREEN_WIDTH, MeasureSpec.EXACTLY), MeasureSpec .makeMeasureSpec(Utils.getPhysicalPixels(WIDTH_OF_BRICK_PREVIEW_IMAGE, getContext()), MeasureSpec.AT_MOST)); view.layout(0, 0, ScreenValues.SCREEN_WIDTH, view.getMeasuredHeight()); view.setDrawingCacheBackgroundColor(Color.TRANSPARENT); view.buildDrawingCache(true); Bitmap bitmap; if (view.getDrawingCache() == null) { view.setDrawingCacheEnabled(drawingCacheEnabled); bitmap = getBitmapFromView(view, getMeasuredWidth(), view.getHeight()); } else { bitmap = Bitmap.createBitmap(view.getDrawingCache()); } view.setDrawingCacheEnabled(drawingCacheEnabled); startDragging(bitmap, touchPointY); if (!dragNewBrick) { setDragViewAnimation(0); dragNewBrick = false; } dragAndDropListener.drag(itemPosition, itemPosition); dimBackground = true; previousItemPosition = itemPosition; return true; } private void startDragging(Bitmap bitmap, int y) { stopDragging(); if (bitmap.getHeight() > maximumDragViewHeight) { bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), maximumDragViewHeight); } ImageView imageView = getGlowingBorder(bitmap); WindowManager.LayoutParams dragViewParameters = createLayoutParameters(); if (isScrolling) { isScrolling = false; dragViewParameters.y = getHeight() / 2 - bitmap.getHeight() / 2; } else { dragViewParameters.y = y - bitmap.getHeight() / 2; } dragViewParameters.windowAnimations = R.style.brick_new; WindowManager windowManager = getWindowManager(); windowManager.addView(imageView, dragViewParameters); dragView = imageView; } public ImageView getGlowingBorder(Bitmap bitmap) { ImageView imageView = new ImageView(getContext()); imageView.setBackgroundColor(DRAG_BACKGROUND_COLOR); imageView.setId(R.id.drag_and_drop_list_view_image_view); Bitmap glowingBitmap = Bitmap.createBitmap(bitmap.getWidth() + 30, bitmap.getHeight() + 30, Bitmap.Config.ARGB_8888); Canvas glowingCanvas = new Canvas(glowingBitmap); Bitmap alpha = bitmap.extractAlpha(); Paint paintBlur = new Paint(); paintBlur.setColor(Color.WHITE); glowingCanvas.drawBitmap(alpha, 15, 15, paintBlur); BlurMaskFilter blurMaskFilter = new BlurMaskFilter(15.0f, BlurMaskFilter.Blur.OUTER); paintBlur.setMaskFilter(blurMaskFilter); glowingCanvas.drawBitmap(alpha, 15, 15, paintBlur); paintBlur.setMaskFilter(null); glowingCanvas.drawBitmap(bitmap, 15, 15, paintBlur); imageView.setImageBitmap(glowingBitmap); return imageView; } private void dragTouchedListItem(int y) { WindowManager.LayoutParams dragViewParameters = (WindowManager.LayoutParams) dragView.getLayoutParams(); dragViewParameters.y = y - dragView.getHeight() / 2; WindowManager windowManager = getWindowManager(); try { windowManager.updateViewLayout(dragView, dragViewParameters); } catch (IllegalArgumentException e) { windowManager.addView(dragView, dragViewParameters); } } private void stopDragging() { if (dragView != null) { dragView.setVisibility(GONE); WindowManager windowManager = getWindowManager(); windowManager.removeView(dragView); dragView.setImageDrawable(null); dragView = null; } } public void resetDraggingScreen() { stopDragging(); dimBackground = false; dragNewBrick = false; invalidate(); } private WindowManager.LayoutParams createLayoutParameters() { WindowManager.LayoutParams windowParameters = new WindowManager.LayoutParams(); windowParameters.gravity = Gravity.TOP | Gravity.LEFT; windowParameters.height = WindowManager.LayoutParams.WRAP_CONTENT; windowParameters.width = WindowManager.LayoutParams.WRAP_CONTENT; windowParameters.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; windowParameters.format = PixelFormat.TRANSLUCENT; return windowParameters; } private WindowManager getWindowManager() { return (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); } private int calculateItemPositionAndTouchPointY(View view) { int itemPosition = -1; int[] location = new int[2]; if (newView) { itemPosition = this.position; View tempView = getChildAt(getChildCount() - 1); if (tempView != null) { tempView.getLocationOnScreen(location); touchPointY = location[1] + (getChildAt(getChildCount() - 1)).getHeight(); } newView = false; } else { itemPosition = pointToPosition(view.getLeft(), view.getTop()); int visiblePosition = itemPosition - getFirstVisiblePosition(); (getChildAt(visiblePosition)).getLocationOnScreen(location); touchPointY = location[1] + (getChildAt(visiblePosition)).getHeight() / 2; } return itemPosition; } private void scrollListWithDraggedItem(int y) { if (y > lowerScrollBound) { smoothScrollBy(SCROLL_SPEED, 0); } else if (y < upperScrollBound) { smoothScrollBy(-SCROLL_SPEED, 0); } } private void dragItemInList(int y, int itemPosition) { int index = previousItemPosition - getFirstVisiblePosition(); if (index > 0) { View upperChild = getChildAt(index - 1); upperDragBound = upperChild.getBottom() - upperChild.getHeight() / 2; } else { upperDragBound = 0; } if (index < getChildCount() - 1) { View lowerChild = getChildAt(index + 1); lowerDragBound = lowerChild.getTop() + lowerChild.getHeight() / 2; } else { lowerDragBound = getHeight(); } if ((y > lowerDragBound || y < upperDragBound)) { if (previousItemPosition != itemPosition) { dragAndDropListener.drag(previousItemPosition, itemPosition); } previousItemPosition = itemPosition; } } public void animateHoveringBrick() { if (dragView == null) { return; } WindowManager.LayoutParams dragViewParameters = (WindowManager.LayoutParams) dragView.getLayoutParams(); long now = System.currentTimeMillis(); if (blinkAnimationTimestamp < now) { dragViewParameters.windowAnimations = R.style.brick_blink; getWindowManager().removeView(dragView); getWindowManager().addView(dragView, dragViewParameters); blinkAnimationTimestamp = now + 800; } } public boolean isCurrentlyDragging() { if (dragView == null) { return false; } return true; } private void setDragViewAnimation(int styleId) { WindowManager.LayoutParams dragViewParameters = (WindowManager.LayoutParams) dragView.getLayoutParams(); dragViewParameters.windowAnimations = styleId; try { getWindowManager().updateViewLayout(dragView, dragViewParameters); } catch (IllegalArgumentException e) { getWindowManager().addView(dragView, dragViewParameters); } } public void setDraggingNewBrick() { dragNewBrick = true; } public void setIsScrolling() { isScrolling = true; } }