基本上,我想要ClipData.Item
的子类,以便我可以发送除CharSequence
,Intent
或URI
以外的数据以及DragEvent
。文档似乎表明这是可能的(请参阅documentation旁边的toString()
方法,该方法专门提到了Item
的子类,但是我尝试过的所有方法都没有用,尽管它们都没有{ {1}}也未ClipData
被宣布为Item
。
我得到的基本设置是扩展final
的内部类,如下所示:
ClipData.Item
TowerButton.java
然后,在接受package com.conundrum.toweroffensenative.app;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.List;
/**
* Created by Nums on 27/03/14.
*/
public class TowerButton extends View {
private Paint mBackgroundPaint, mTowerPaint, mShadowPaint, mLabelPaint, mDisabledPaint;
private List<Tower> mTowers;
private Path mTowerPath;
private DragShadowBuilder mDragShadowBuilder;
private Rect r;
public TowerButton(Context context, AttributeSet attrs, List<Tower> towers) {
super(context, attrs);
mTowers = towers;
init();
}
// If I need a tower type that starts with 0 stock, add constructor which takes Paint/Path as args
private void init() {
mBackgroundPaint = new Paint();
mBackgroundPaint.setStyle(Paint.Style.FILL);
mBackgroundPaint.setColor(Color.GRAY);
mTowerPaint = mTowers.get(0).getPaint();
mTowerPath = mTowers.get(0).getPath();
mShadowPaint = new Paint(mTowerPaint);
mShadowPaint.setAlpha(150);
mDisabledPaint = new Paint(mTowerPaint);
mDisabledPaint.setColor(Color.LTGRAY);
mDisabledPaint.setAlpha(150);
mLabelPaint = new Paint();
mLabelPaint.setTextSize(28);
mLabelPaint.setTextAlign(Paint.Align.CENTER);
mLabelPaint.setAntiAlias(true);
mLabelPaint.setColor(Color.WHITE);
mDragShadowBuilder = new DragShadowBuilder(this) {
@Override
public void onDrawShadow(Canvas canvas) {
canvas.drawPath(mTowerPath, mShadowPaint);
}
};
setTag(mTowers.get(0).getClass().getName() + "Button");
r = new Rect();
}
public String getQuantity() {
return String.valueOf(mTowers.size());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Matrix pathMatrix = new Matrix();
RectF pathBounds = new RectF();
mTowerPath.computeBounds(pathBounds, false);
pathMatrix.setScale(w / pathBounds.width(), h / pathBounds.height());
mTowerPath.transform(pathMatrix);
r.set(0, 0, w, h);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(r, mBackgroundPaint);
if (mTowers.size() > 0) {
canvas.drawPath(mTowerPath, mTowerPaint);
canvas.drawText(getQuantity(), getX() + (getWidth() / 2), getY() + (getHeight() / 2), mLabelPaint);
} else {
canvas.drawPath(mTowerPath, mDisabledPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN && mTowers.size() > 0) {
Tower dragTower = mTowers.get(0);
TowerItem item = new TowerItem(dragTower);
ClipData dragData = new ClipData(dragTower.getBuildRow(),
new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN }, item);
startDrag(dragData, mDragShadowBuilder, null, 0);
return true;
}
return false;
}
public Tower giveTower() {
// TODO: need checking to ensure size > 0?
Tower tower = mTowers.remove(0);
invalidate();
return tower;
}
public void recycleTower(Tower tower) {
mTowers.add(tower);
invalidate();
}
public static class TowerItem extends ClipData.Item {
final Tower mTower;
public TowerItem(Tower tower) {
super("");
mTower = tower;
}
public Tower getTower() {
return mTower;
}
@Override
public CharSequence coerceToText(Context context) {
if (mTower != null) {
return mTower.getClass().getName();
}
return super.coerceToText(context);
}
}
}
:
DropEvent
TowerView.java
没有编译时错误。但是,在尝试在运行时执行转换时,我得到了异常:
package com.conundrum.toweroffensenative.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.ClipData;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.DragEvent;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
/**
* Created by Nums on 24/03/14.
*/
public class TowerView extends View {
private Paint mBasePaint, mHighlightPaint, mStunnedPaint, mSelectedPaint;
private Tower mTower;
private Path mTowerPath;
private Paint mTowerPaint;
private boolean highlighted;
private boolean stunned;
private boolean selected;
private int mIndex;
private List<TowerView> mNeighbours;
private Rect r;
private class mListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
for (TowerView tv : mNeighbours) {
tv.highlighted ^= true;
tv.invalidate();
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
List<TowerView> myRow = ((TowerGrid) getParent()).getRow(mIndex % TowerGrid.ROWS);
for (TowerView v : myRow) {
v.stunned ^= true;
v.invalidate();
}
}
}
GestureDetector mDetector = new GestureDetector(TowerView.this.getContext(), new mListener());
Callable<Void> mStartRecycleCallable = new Callable<Void>() {
@Override
public Void call() throws Exception {
startRecycle();
return null;
}
};
Callable<Void> mRecycleCallable = new Callable<Void>() {
@Override
public Void call() throws Exception {
recycle();
return null;
}
};
public TowerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mIndex = -1;
mNeighbours = new ArrayList<TowerView>();
highlighted = false;
stunned = false;
selected = false;
LinearGradient baseGradient = new LinearGradient(0, 0, 0, 25,
new int[] {Color.LTGRAY, Color.DKGRAY}, null, Shader.TileMode.MIRROR);
LinearGradient highlightGradient = new LinearGradient(0, 0, 0, 25,
new int[] {Color.YELLOW, Color.RED}, null, Shader.TileMode.MIRROR);
LinearGradient stunnedGradient = new LinearGradient(0, 0, 0, 25,
new int[] {Color.CYAN, Color.BLUE}, null, Shader.TileMode.MIRROR);
mBasePaint = new Paint();
mBasePaint.setShader(baseGradient);
mHighlightPaint = new Paint();
mHighlightPaint.setShader(highlightGradient);
mStunnedPaint = new Paint();
mStunnedPaint.setShader(stunnedGradient);
mSelectedPaint = new Paint();
mSelectedPaint.setStyle(Paint.Style.STROKE);
mSelectedPaint.setColor(Color.GREEN);
mSelectedPaint.setStrokeWidth(5);
r = new Rect();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
r.set(0, 0, w, h);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the tower base in one of three styles
if (highlighted) {
canvas.drawRect(r, mHighlightPaint);
} else if (stunned) {
canvas.drawRect(r, mStunnedPaint);
} else {
canvas.drawRect(r, mBasePaint);
}
if (mTower != null) {
canvas.drawPath(mTowerPath, mTowerPaint);
}
if (selected) {
canvas.drawRect(r, mSelectedPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = mDetector.onTouchEvent(event);
if (!result) {
// Custom gesture code
}
return result;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = super.dispatchTouchEvent(event);
return result;
}
@Override
public boolean onDragEvent(DragEvent event) {
final int action = event.getAction();
switch(action) {
case DragEvent.ACTION_DRAG_STARTED:
// check if Tower can be built on this col - in case I allow that to differ per Tower
if (mIndex / TowerGrid.ROWS == Integer.parseInt(event.getClipDescription().getLabel().toString())) {
selected = true;
invalidate();
return true;
}
return false;
case DragEvent.ACTION_DRAG_ENTERED:
highlighted = true;
invalidate();
break;
case DragEvent.ACTION_DRAG_EXITED:
highlighted = false;
invalidate();
break;
case DragEvent.ACTION_DROP:
ClipData.Item item = event.getClipData().getItemAt(0);
if (item instanceof TowerButton.TowerItem) {
Log.d("towerview", "SUCCESS!");
}
// Always returns false
TowerButton.TowerItem tItem = (TowerButton.TowerItem) item; // exception
Tower dragTower = item.getTower();
setTower(dragTower);
return true;
case DragEvent.ACTION_DRAG_ENDED:
highlighted = false;
selected = false;
invalidate();
return true;
}
return false;
}
public void setTower(Tower tower) {
if (mTower != null) {
TowerButton button = (TowerButton) getRootView().findViewWithTag(mTower.getClass().getName() + "Button");
button.recycleTower(mTower);
}
mTower = tower;
mTowerPaint = tower.getPaint();
mTowerPath = tower.getPath();
Matrix pathMatrix = new Matrix();
RectF pathBounds = new RectF();
mTowerPath.computeBounds(pathBounds, false);
pathMatrix.setScale(getWidth() / pathBounds.width(), getHeight() / pathBounds.height());
mTowerPath.transform(pathMatrix);
invalidate();
}
public boolean advance(int distance) {
if (!stunned) {
// first account for the new view being added
setTranslationX(getTranslationX() - distance);
// then animate right over 1000 ms
ViewPropertyAnimator animator = animate().translationXBy(distance).setDuration(1000);
addCompatibilityAnimationCallback(animator, mStartRecycleCallable).start();
return true;
}
return false;
}
private void startRecycle() {
if (mIndex / TowerGrid.ROWS == TowerGrid.COLS - 1) {
ViewPropertyAnimator animator = animate().translationXBy(getWidth() / -2).scaleX(0).setDuration(1000);
addCompatibilityAnimationCallback(animator, mRecycleCallable).start();
}
}
private void recycle() {
if (mTower != null) {
TowerButton button = (TowerButton) getRootView().findViewWithTag(mTower.getClass().getName() + "Button");
button.recycleTower(mTower);
}
((ViewGroup) getParent()).removeView(this);
}
public void updateNeighbours() {
ViewGroup parent = (ViewGroup) getParent();
mIndex = parent.indexOfChild(this);
mNeighbours.clear();
if (mIndex >= TowerGrid.ROWS) {
mNeighbours.add((TowerView) parent.getChildAt(mIndex - TowerGrid.ROWS));
}
if (mIndex < TowerGrid.ROWS * (TowerGrid.COLS - 2)) {
mNeighbours.add((TowerView) parent.getChildAt(mIndex + TowerGrid.ROWS));
}
if (mIndex % TowerGrid.ROWS != 0) {
mNeighbours.add((TowerView) parent.getChildAt(mIndex - 1));
}
if (mIndex % TowerGrid.ROWS != TowerGrid.ROWS - 1) {
mNeighbours.add((TowerView) parent.getChildAt(mIndex + 1));
}
}
private ViewPropertyAnimator addCompatibilityAnimationCallback(ViewPropertyAnimator animator, final Callable<Void> callbackFunc) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
animator.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
try {
callbackFunc.call();
} catch (Exception e) {
e.printStackTrace();
}
}
});
} else {
animator.withEndAction(new Runnable() {
@Override
public void run() {
try {
callbackFunc.call();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
return animator;
}
}
同样,java.lang.ClassCastException: android.content.ClipData$Item cannot be cast to com.conundrum.toweroffensenative.app.TowerButton$TowerItem
之类的代码会返回false,即使item instanceof TowerButton.TowerItem
显然正在扩展TowerItem
。
我是否缺少某些东西阻止这些类被子类化?或者我做错了什么?我知道我可以使用ClipData.Item
和ContentProvider
来传输更复杂的信息,但是当传输的数据永远不必在此应用程序之外可用时,这似乎有点过分了。
修改
我还尝试使用自己的URI
内部类创建ClipData
的子类,以便我可以覆盖Item
以返回getItem()
- 然后我需要将TowerItem
转换为ClipData
,但失败时会出现同样的错误。
编辑3
包括两个相关文件的全部内容。
答案 0 :(得分:0)
TowerItem
来自ClipData.Item
,因此TowerItem
是ClipData.Item
,但反过来并非总是如此。 ClipData.Item
可以是TowerItem
,但不一定。
通过将ClipData.Item
显式转换为TowerItem
:(TowerItem)ClipData.Item
来避免编译错误。但是你无法避免运行时错误。
instanceOf
的正确用法应该是这样的:
if(event.getClipData().getItemAt(0) instanceOf TowerButton.TowerItem) {
TowerButton.TowerItem item = (TowerButton.TowerItem) event.getClipData().getItemAt(0);
}
答案 1 :(得分:0)
类似的问题,我一直在为此考虑系统代码。基本上,ClipData是一个Parcelable,所以我认为ClipData.Item的一个子类没有一个敏感的ClipData子类,它识别你的自定义ClipData.Item将转换为一个Charsequence然后作为基本文本返回ClipData.Item。否则,您需要为URI格式创建一个ContentProvider,对于这个用例,单个应用程序中的ui是过度的,可能是错误的。 我对此的破解最终是将ClipData中的标识标记作为文本传递,并使拖动启动视图(通过状态传递到事件数据中)负责将其转换为对象。不完全干净但不完全丑陋。