/* * Copyright 2015. Appsi Mobile * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.appsimobile.appsii.hotspotmanager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Rect; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.appsimobile.appsii.AppsiApplication; import com.appsimobile.appsii.HotspotItem; import com.appsimobile.appsii.HotspotItem.ConfigurationListener; import com.appsimobile.appsii.R; import com.appsimobile.appsii.compat.MapCompat; import java.util.Map; /** * A view that allows the user to move a hotspot to a different position. * <p/> * This is largely based on the old-appsi's implementation and needs documentation */ public class HotspotPositionEditorView extends ViewGroup implements View.OnTouchListener, ConfigurationListener { public static final int VIEWHOLDER_IDX_INDICATOR_LEFT = 0; public static final int VIEWHOLDER_IDX_INDICATOR_RIGHT = 1; private final Map<HotspotItem, LayoutTag> mLayoutTags = MapCompat.createMap(); boolean mAllowLongPress = true; int mBitmapDragX; int mBitmapDragY; int mDragX; int mDragY; int mDragOffsetX; int mDragOffsetY; boolean mDragLeft; Bitmap mDragBitmap; boolean mHandlingChange; boolean mDragging; final Rect mDragRect = new Rect(); OnPositionChangedListener mOnPositionChangedListener; private HotspotItem mHotspotItem; private View mHotspotView; private LayoutInflater mInflater; private int mInitialTouchX; private int mInitialTouchY; public HotspotPositionEditorView(Context context) { super(context); init(); } public HotspotPositionEditorView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HotspotPositionEditorView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } void init() { } public void setOnPositionChangedListener( OnPositionChangedListener onPositionChangedListener) { mOnPositionChangedListener = onPositionChangedListener; } public void setHotspotItem(HotspotItem configuration) { mHotspotItem = configuration; updateViews(); } private void updateViews() { if (mInflater == null) { mInflater = LayoutInflater.from(getContext()); } removeAllViews(); HotspotItem conf = mHotspotItem; mHotspotView = setUpView(conf); mLayoutTags.put(conf, new LayoutTag(conf.mLeft, conf.mYPosRelativeToView, conf.mHeightRelativeToViewHeight)); } private View setUpView(HotspotItem conf) { View v = mInflater.inflate(R.layout.plugin_view, this, false); View positionIndicatorLeft = v.findViewById(R.id.position_indicator_left); View positionIndicatorRight = v.findViewById(R.id.position_indicator_right); View clickHandle = v.findViewById(R.id.plugin_click_region); clickHandle.setTag(conf); clickHandle.setOnTouchListener(this); TextView nameView = (TextView) v.findViewById(R.id.plugin_name); String preset = conf.mName; nameView.setText(preset); if (conf.mLeft) { positionIndicatorRight.setVisibility(View.GONE); } else { positionIndicatorLeft.setVisibility(View.GONE); } v.setTag(R.id.viewholder, new View[]{positionIndicatorLeft, positionIndicatorRight}); v.setTag(conf); HotspotItemLayoutParams param = new HotspotItemLayoutParams(1); param.mIsLeft = conf.mLeft; addView(v, param); conf.setConfigurationListener(this); return v; } @Override public void onHotspotConfigurationChanged(HotspotItem hotspotItem, float heightRelativeToViewHeight, float old) { if (mHandlingChange) return; mHandlingChange = true; View v = mHotspotView; if (v != null) { HotspotItemLayoutParams lp = (HotspotItemLayoutParams) v.getLayoutParams(); int y = lp.mYPosition; int parentHeight = getHeight(); int height = (int) (parentHeight * heightRelativeToViewHeight); if (y + height > parentHeight) { height = parentHeight - y; hotspotItem.setHeightRelativeToViewHeight(old); } lp.mHeight = height; requestLayout(); } mHandlingChange = false; } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() != MotionEvent.ACTION_DOWN) return false; if (mAllowLongPress) { mDragging = true; startDrag(v); return true; } else { mAllowLongPress = true; return false; } } private void startDrag(View v) { mDragBitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Config.ARGB_8888); Canvas tmp = new Canvas(); tmp.setBitmap(mDragBitmap); v.draw(tmp); mDragRect.set(0, 0, v.getWidth(), v.getHeight()); offsetDescendantRectToMyCoords(v, mDragRect); mDragOffsetX = mInitialTouchX - mDragRect.left; mDragOffsetY = mInitialTouchY - mDragRect.top; mBitmapDragX = mInitialTouchX - mDragOffsetX; mBitmapDragY = mInitialTouchY - mDragOffsetY; updateLayoutParamsForDrag(); invalidate(); } private void updateLayoutParamsForDrag() { int count = getChildCount(); for (int i = 0; i < count; i++) { View v = getChildAt(i); HotspotItemLayoutParams lp = (HotspotItemLayoutParams) v.getLayoutParams(); lp.mDragIsLeft = lp.mIsLeft; lp.mDragYposition = lp.mYPosition; // lp.mHeight = lp.mD } requestLayout(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mInitialTouchX = (int) ev.getX(); mInitialTouchY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: return mDragging; } return super.onInterceptTouchEvent(ev); } @Override protected void dispatchDraw(@NonNull Canvas canvas) { super.dispatchDraw(canvas); if (mDragging && mDragBitmap != null) { canvas.save(); canvas.translate(mBitmapDragX, mBitmapDragY); canvas.translate(0, mDragBitmap.getHeight() / 2); canvas.scale(1.2f, 1.2f); canvas.translate(0, mDragBitmap.getHeight() / -2); canvas.drawBitmap(mDragBitmap, 0, 0, null); canvas.restore(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); int h = b - t; int w = r - l; for (int i = 0; i < count; i++) { View child = getChildAt(i); HotspotItemLayoutParams params = (HotspotItemLayoutParams) child.getLayoutParams(); if (changed) { params.setParentHeight(h); } if (mDragging) { boolean left = params.mDragIsLeft; if (left) { child.layout(0, params.mDragYposition, child.getMeasuredWidth(), params.mDragYposition + params.mHeight); } else { child.layout(w - child.getMeasuredWidth(), params.mDragYposition, w, params.mDragYposition + params.mHeight); } } else { boolean left = params.mIsLeft; if (left) { child.layout(0, params.mYPosition, child.getMeasuredWidth(), params.mYPosition + params.mHeight); } else { child.layout(w - child.getMeasuredWidth(), params.mYPosition, w, params.mYPosition + params.mHeight); } } } } @Override public boolean onTouchEvent(@NonNull MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: if (mDragging) { if (mDragBitmap != null) { invalidate(mBitmapDragX, mBitmapDragY, mDragBitmap.getWidth() + mBitmapDragX, mBitmapDragY + mDragBitmap.getHeight()); } int x = (int) ev.getX(); int y = (int) ev.getY(); int deltaX = x - mInitialTouchX; int deltaY = y - mInitialTouchY; mBitmapDragX = mInitialTouchX + deltaX - mDragOffsetX; mBitmapDragY = mInitialTouchY + deltaY - mDragOffsetY; mDragX = deltaX; mDragY = deltaY; mDragLeft = x < getWidth() / 2; HotspotItemLayoutParams lp = (HotspotItemLayoutParams) mHotspotView.getLayoutParams(); lp.updateDrag(mBitmapDragX, mBitmapDragY); updateViewLocationsForDrag(); if (mDragBitmap != null) { invalidate(mBitmapDragX, mBitmapDragY, mDragBitmap.getWidth() + mBitmapDragX, mBitmapDragY + mDragBitmap.getHeight()); } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mDragging) { HotspotItemLayoutParams lp = (HotspotItemLayoutParams) mHotspotView.getLayoutParams(); lp.endDrag(); invalidate(); mAllowLongPress = true; mDragging = false; mDragBitmap = null; snapDraggedViewToPosition(); } break; } return !mAllowLongPress; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Context context = getContext(); int count = getChildCount(); int ph = getMeasuredHeight(); int i10 = (int) (5 * AppsiApplication.getDensity(context)); for (int i = 0; i < count; i++) { View child = getChildAt(i); HotspotItemLayoutParams params = (HotspotItemLayoutParams) child.getLayoutParams(); HotspotItem c = (HotspotItem) child.getTag(); if (c != null && !params.mIsUpdated) { params.mYPosition = (int) (ph * c.mYPosRelativeToView); params.mHeight = (int) (ph * c.mHeightRelativeToViewHeight); params.mIsUpdated = true; } int h = params.mHeight; int w = getMeasuredWidth() / 2 - i10; int wspec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY); int hspec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY); child.measure(wspec, hspec); } } private void updateViewLocationsForDrag() { View dragged = mHotspotView; HotspotItemLayoutParams lp = (HotspotItemLayoutParams) dragged.getLayoutParams(); if (lp.mDragIsLeft != mDragLeft) { View[] vh = (View[]) dragged.getTag(R.id.viewholder); View left = vh[VIEWHOLDER_IDX_INDICATOR_LEFT]; View right = vh[VIEWHOLDER_IDX_INDICATOR_RIGHT]; if (mDragLeft) { left.setVisibility(View.VISIBLE); right.setVisibility(View.GONE); } else { left.setVisibility(View.GONE); right.setVisibility(View.VISIBLE); } lp.mDragIsLeft = mDragLeft; requestLayout(); invalidate(); } lp.mDragYposition = lp.mYPosition + mDragY; requestLayout(); invalidate(); } private void snapDraggedViewToPosition() { int parentHeight = getHeight(); View v = mHotspotView; HotspotItemLayoutParams lp = (HotspotItemLayoutParams) v.getLayoutParams(); lp.mIsLeft = lp.mDragIsLeft; lp.mYPosition = lp.mDragYposition; if (lp.mYPosition < 0) { lp.mYPosition = 0; } if (lp.mYPosition + lp.mHeight > parentHeight) { lp.mYPosition = parentHeight - lp.mHeight; } requestLayout(); notifyPositionChanged(mHotspotItem.mId, lp.mIsLeft, lp.mYPosition); } private void notifyPositionChanged(long id, boolean isLeft, int yPosition) { if (mOnPositionChangedListener != null) { float newY = yPosition / (float) getHeight(); mOnPositionChangedListener.onPositionChanged(id, isLeft, newY); } } interface OnPositionChangedListener { void onPositionChanged(long hotspotId, boolean isLeft, float yPosition); } static class HotspotItemLayoutParams extends LayoutParams { int mYPosition; int mHeight; int mParentHeight; boolean mIsLeft; boolean mIsDragView; int mDraggedX; int mDraggedY; int mDragYposition; boolean mDragIsLeft; boolean mIsUpdated; public HotspotItemLayoutParams(int parentHeight) { super(0, 0); mParentHeight = parentHeight; } void setParentHeight(int height) { mParentHeight = height; updateConfiguration(); } private void updateConfiguration() { } public void updateDrag(int dragX, int dragY) { mIsDragView = true; mDraggedX = dragX; mDraggedY = dragY; } public void endDrag() { mIsDragView = false; } } static class LayoutTag { final float mHeight; final float mYpos; final boolean mLeft; public LayoutTag(boolean left, float yposRelativeToView, float heightRelativeToViewHeight) { mLeft = left; mHeight = heightRelativeToViewHeight; mYpos = yposRelativeToView; } } }