Xamarin Android Vertical ViewPager - java

I need a vertical ViewPager in Android using Xamarin and the solution doesn't work. I searched some examples in java but there's an object developed by the git community that do all the work. Unfortunately there isn't in Xamarin. So, this is my code, it doesn't give me error, but it displays only a black screen. Nothing more.
I Extended the ViewPager class
public class VerticalViewPager : ViewPager {
public VerticalViewPager (Context context):base(context) {
Init ();
}
public VerticalViewPager(Context context, IAttributeSet attr):base(context, attr) {
Init();
}
public override bool OnTouchEvent (Android.Views.MotionEvent e) {
e.SetLocation (e.GetY (), e.GetX ());
return base.OnTouchEvent (e);
}
private void Init() {
SetPageTransformer (true, new PagerTransformer());
OverScrollMode = Android.Views.OverScrollMode.Never;
}
}
and create my PageTransformer
public class PagerTransformer : Java.Lang.Object, ViewPager.IPageTransformer {
int pageWidth;
int pageHeight;
float yPos;
public PagerTransformer () {}
public void TransformPage (View view, float position) {
pageWidth = view.Width;
pageHeight = view.Height;
if (position < -1) {
view.Alpha = 0;
}
else if (position <= 1) {
view.Alpha = 1;
// Counteract the default slide transition
view.TranslationX = (pageWidth * -position);
//set Y position to swipe in from top
yPos = position + pageHeight;
view.TranslationY = yPos;
}
else {
// This page is way off-screen to the right.
view.Alpha = 0;
}
}
}
In the MainActivity, onCreate method i set
page = FindViewById<VerticalViewPager> (Resource.Id.vertical_pager);
and obviously page is a VerticalViewPager object.
If I use a normale ViewPager, the app works fine. Any ideas about the reason of the black screen?
Any java code is appreciare as well!
Thanks

The reason of black screen is that you set your screen position out of visible bounds. Your visible bounds are from 0 to page height.
Try his : "yPos = position * pageHeight;" instead of "yPos = position + pageHeight;"

Related

How to imitate the visuals of MaterialCardView using a MaterialShapeDrawable

What I've tried?
After having a brief look through the MaterailCardViewHelper source, I tried to replicate the way it draws the associated Drawables. Unfortunately, it results in a black shape with some "treated" corners and looks nothing like the MaterialCardView. I understand the MaterialCardViewHelper applies the background and foreground on the actual CardView and after having looked at the source for that, it doesn't appear to be doing anything special, that is, it just seems to call setBackgroundDrawable (which I am doing on someView, as shown below).
I am using Xamarin so my code is written in C#. I've essentially converted the Java source (of the MaterialCardViewHelper) to its C# equivalent, replacing references of "materialCardView" to MaterialCardDrawable where appropriate.
I've tried to keep the code as close to the original Java source to ensure anyone reading this can easily compare the original with mine. I've changed only enough to make the code compile. The main difference is the "Draw" method which I assume is where my issue lies.
public sealed class MaterialCardDrawable : MaterialShapeDrawable
{
private static readonly int[] CHECKED_STATE_SET = { Android.Resource.Attribute.StateChecked };
private static readonly int DEFAULT_STROKE_VALUE = -1;
private static readonly double COS_45 = Math.Cos(Math.ToRadians(45));
private static readonly float CARD_VIEW_SHADOW_MULTIPLIER = 1.5f;
private static readonly int CHECKED_ICON_LAYER_INDEX = 2;
// this class will act as MaterialCardView (so any references to "materialCardView" will just be referenced to this class instead)
//private readonly MaterialCardView materialCardView;
private readonly Rect userContentPadding = new Rect();
private readonly MaterialShapeDrawable bgDrawable;
private readonly MaterialShapeDrawable foregroundContentDrawable;
private int checkedIconMargin;
private int checkedIconSize;
private int strokeWidth;
private Drawable fgDrawable;
private Drawable checkedIcon;
private ColorStateList rippleColor;
private ColorStateList checkedIconTint;
private ShapeAppearanceModel shapeAppearanceModel;
private ColorStateList strokeColor;
private Drawable rippleDrawable;
private LayerDrawable clickableForegroundDrawable;
private MaterialShapeDrawable compatRippleDrawable;
private MaterialShapeDrawable foregroundShapeDrawable;
private bool isBackgroundOverwritten = false;
private bool checkable;
public MaterialCardDrawable(Context context)
{
bgDrawable = new MaterialShapeDrawable(context, null, 0, 0); // different
bgDrawable.InitializeElevationOverlay(context);
bgDrawable.SetShadowColor(Color.DarkGray/*potentially different*/);
ShapeAppearanceModel.Builder shapeAppearanceModelBuilder = bgDrawable.ShapeAppearanceModel.ToBuilder();
shapeAppearanceModelBuilder.SetAllCornerSizes(DimensionHelper.GetPixels(4)); // different, use 4 as opposed to 0 as default (converts dp to pixels)
foregroundContentDrawable = new MaterialShapeDrawable();
setShapeAppearanceModel(shapeAppearanceModelBuilder.Build());
loadFromAttributes(context);
}
// assuming responsibility for drawing the rest of the drawables
public override void Draw(Canvas canvas)
{
bgDrawable?.Draw(canvas);
clickableForegroundDrawable?.Draw(canvas);
compatRippleDrawable?.Draw(canvas);
fgDrawable?.Draw(canvas);
foregroundContentDrawable?.Draw(canvas);
foregroundShapeDrawable?.Draw(canvas);
rippleDrawable?.Draw(canvas);
}
public override void SetBounds(int left, int top, int right, int bottom)
{
base.SetBounds(left, top, right, bottom);
bgDrawable?.SetBounds(left, top, right, bottom);
clickableForegroundDrawable?.SetBounds(left, top, right, bottom);
compatRippleDrawable?.SetBounds(left, top, right, bottom);
fgDrawable?.SetBounds(left, top, right, bottom);
foregroundContentDrawable?.SetBounds(left, top, right, bottom);
foregroundShapeDrawable?.SetBounds(left, top, right, bottom);
rippleDrawable?.SetBounds(left, top, right, bottom);
}
void loadFromAttributes(Context context)
{
// this is very different to the original source
// just use default values
strokeColor = ColorStateList.ValueOf(new Color(DEFAULT_STROKE_VALUE));
strokeWidth = 0;
checkable = false;
// ignore checkedIcon related calls for testing purposes
TypedArray attributes = context.ObtainStyledAttributes(new int[] { Android.Resource.Attribute.ColorControlHighlight, Android.Resource.Attribute.ColorForeground });
rippleColor = ColorStateList.ValueOf(attributes.GetColor(0, 0));
ColorStateList foregroundColor = attributes.GetColorStateList(1);
setCardForegroundColor(foregroundColor);
updateRippleColor();
updateElevation();
updateStroke();
fgDrawable = /*materialCardView.*/isClickable() ? getClickableForeground() : foregroundContentDrawable;
}
// original source calls "materialCardView" but this class simply mimicks the MaterialCardView so this method exists here
bool isClickable()
{
return false;
}
// original source calls "materialCardView" but this class simply mimicks the MaterialCardView so this method exists here
float getMaxCardElevation()
{
// apparently used for when dragging to clamp the shadow
// using this as a default value
return DimensionHelper.GetPixels(12);
}
// original source calls "materialCardView" but this class simply mimicks the MaterialCardView so this method exists here
float getCardViewRadius()
{
// just using a radius of 4dp for now
return DimensionHelper.GetPixels(4);
}
// original source calls "materialCardView" but this class simply mimicks the MaterialCardView so this method exists here
bool getUseCompatPadding()
{
// no effect when API version is Lollipop and beyond
return false;
}
// original source calls "materialCardView" but this class simply mimicks the MaterialCardView so this method exists here
bool getPreventCornerOverlap()
{
// no effect when API version is Lollipop and beyond
return false;
}
bool getIsBackgroundOverwritten()
{
return isBackgroundOverwritten;
}
void setBackgroundOverwritten(bool isBackgroundOverwritten)
{
this.isBackgroundOverwritten = isBackgroundOverwritten;
}
void setStrokeColor(ColorStateList strokeColor)
{
if (this.strokeColor == strokeColor)
{
return;
}
this.strokeColor = strokeColor;
updateStroke();
}
int getStrokeColor()
{
return strokeColor == null ? DEFAULT_STROKE_VALUE : strokeColor.DefaultColor;
}
ColorStateList getStrokeColorStateList()
{
return strokeColor;
}
void setStrokeWidth(int strokeWidth)
{
if (strokeWidth == this.strokeWidth)
{
return;
}
this.strokeWidth = strokeWidth;
updateStroke();
}
int getStrokeWidth()
{
return strokeWidth;
}
MaterialShapeDrawable getBackground()
{
return bgDrawable;
}
void setCardBackgroundColor(ColorStateList color)
{
bgDrawable.FillColor = color;
}
ColorStateList getCardBackgroundColor()
{
return bgDrawable.FillColor;
}
void setCardForegroundColor(ColorStateList foregroundColor)
{
foregroundContentDrawable.FillColor = foregroundColor == null ? ColorStateList.ValueOf(Color.Transparent) : foregroundColor;
}
ColorStateList getCardForegroundColor()
{
return foregroundContentDrawable.FillColor;
}
void setUserContentPadding(int left, int top, int right, int bottom)
{
userContentPadding.Set(left, top, right, bottom);
updateContentPadding();
}
Rect getUserContentPadding()
{
return userContentPadding;
}
void updateClickable()
{
Drawable previousFgDrawable = fgDrawable;
fgDrawable = /*materialCardView.*/isClickable() ? getClickableForeground() : foregroundContentDrawable;
if (previousFgDrawable != fgDrawable)
{
updateInsetForeground(fgDrawable);
}
}
void setCornerRadius(float cornerRadius)
{
setShapeAppearanceModel(shapeAppearanceModel.WithCornerSize(cornerRadius));
fgDrawable.InvalidateSelf();
if (shouldAddCornerPaddingOutsideCardBackground()
|| shouldAddCornerPaddingInsideCardBackground())
{
updateContentPadding();
}
if (shouldAddCornerPaddingOutsideCardBackground())
{
updateInsets();
}
}
float getCornerRadius()
{
return bgDrawable.TopLeftCornerResolvedSize;
}
void setProgress(float progress)
{
bgDrawable.Interpolation = progress;
if (foregroundContentDrawable != null)
{
foregroundContentDrawable.Interpolation = progress;
}
if (foregroundShapeDrawable != null)
{
foregroundShapeDrawable.Interpolation = progress;
}
}
float getProgress()
{
return bgDrawable.Interpolation;
}
void updateElevation()
{
bgDrawable.Elevation = 4; // different for simplicity's sake use a default value of 4
}
void updateInsets()
{
// No way to update the inset amounts for an InsetDrawable, so recreate insets as needed.
if (!getIsBackgroundOverwritten())
{
// this is unavailable outside of "material-components" package
//materialCardView.setBackgroundInternal(insetDrawable(bgDrawable));
// maybe a call to
// InvalidateSelf()
// works in place of the above?
}
// can't find this in the original "MaterialCardView" or "CardView" source, any ideas?
// I assume it's on a base class, like "FrameLayout" but I couldn't find it there either
//materialCardView.setForeground(insetDrawable(fgDrawable));
// don't know enough about the above to provide a replacement call, any ideas?
}
void updateStroke()
{
foregroundContentDrawable.SetStroke(strokeWidth, strokeColor);
}
void updateContentPadding()
{
bool includeCornerPadding = shouldAddCornerPaddingInsideCardBackground() || shouldAddCornerPaddingOutsideCardBackground();
// The amount with which to adjust the user provided content padding to account for stroke and
// shape corners.
int contentPaddingOffset = (int)((includeCornerPadding ? calculateActualCornerPadding() : 0) - getParentCardViewCalculatedCornerPadding());
// this is unavailable outside of "material-components" package
// and possibly not required to simulate this
//materialCardView.setAncestorContentPadding(
// userContentPadding.left + contentPaddingOffset,
// userContentPadding.top + contentPaddingOffset,
// userContentPadding.right + contentPaddingOffset,
// userContentPadding.bottom + contentPaddingOffset);
}
void setCheckable(bool checkable)
{
this.checkable = checkable;
}
bool isCheckable()
{
return checkable;
}
void setRippleColor(ColorStateList rippleColor)
{
this.rippleColor = rippleColor;
updateRippleColor();
}
void setCheckedIconTint(ColorStateList checkedIconTint)
{
this.checkedIconTint = checkedIconTint;
if (checkedIcon != null)
{
DrawableCompat.SetTintList(checkedIcon, checkedIconTint);
}
}
ColorStateList getCheckedIconTint()
{
return checkedIconTint;
}
ColorStateList getRippleColor()
{
return rippleColor;
}
Drawable getCheckedIcon()
{
return checkedIcon;
}
void setCheckedIcon(Drawable checkedIcon)
{
this.checkedIcon = checkedIcon;
if (checkedIcon != null)
{
this.checkedIcon = DrawableCompat.Wrap(checkedIcon.Mutate());
DrawableCompat.SetTintList(this.checkedIcon, checkedIconTint);
}
if (clickableForegroundDrawable != null)
{
Drawable checkedLayer = createCheckedIconLayer();
clickableForegroundDrawable.SetDrawableByLayerId(Resource.Id.mtrl_card_checked_layer_id, checkedLayer);
}
}
int getCheckedIconSize()
{
return checkedIconSize;
}
void setCheckedIconSize(int checkedIconSize)
{
this.checkedIconSize = checkedIconSize;
}
int getCheckedIconMargin()
{
return checkedIconMargin;
}
void setCheckedIconMargin(int checkedIconMargin)
{
this.checkedIconMargin = checkedIconMargin;
}
void onMeasure(int measuredWidth, int measuredHeight)
{
if (clickableForegroundDrawable != null)
{
int left = measuredWidth - checkedIconMargin - checkedIconSize;
int bottom = measuredHeight - checkedIconMargin - checkedIconSize;
bool isPreLollipop = VERSION.SdkInt < Android.OS.BuildVersionCodes.Lollipop;
if (isPreLollipop || /*materialCardView.*/getUseCompatPadding())
{
bottom -= (int)Math.Ceil(2f * calculateVerticalBackgroundPadding());
left -= (int)Math.Ceil(2f * calculateHorizontalBackgroundPadding());
}
int right = checkedIconMargin;
// potentially not required for this use case
//if (ViewCompat.GetLayoutDirection(materialCardView) == ViewCompat.LayoutDirectionRtl)
//{
// // swap left and right
// int tmp = right;
// right = left;
// left = tmp;
//}
clickableForegroundDrawable.SetLayerInset(CHECKED_ICON_LAYER_INDEX, left, checkedIconMargin /* top */, right, bottom);
}
}
void forceRippleRedraw()
{
if (rippleDrawable != null)
{
Rect bounds = rippleDrawable.Bounds;
// Change the bounds slightly to force the layer to change color, then change the layer again.
// In API 28 the color for the Ripple is snapshot at the beginning of the animation,
// it doesn't update when the drawable changes to android:state_checked.
int bottom = bounds.Bottom;
rippleDrawable.SetBounds(bounds.Left, bounds.Top, bounds.Right, bottom - 1);
rippleDrawable.SetBounds(bounds.Left, bounds.Top, bounds.Right, bottom);
}
}
void setShapeAppearanceModel(ShapeAppearanceModel shapeAppearanceModel)
{
this.shapeAppearanceModel = shapeAppearanceModel;
bgDrawable.ShapeAppearanceModel = shapeAppearanceModel;
bgDrawable.SetShadowBitmapDrawingEnable(!bgDrawable.IsRoundRect);
if (foregroundContentDrawable != null)
{
foregroundContentDrawable.ShapeAppearanceModel = shapeAppearanceModel;
}
if (foregroundShapeDrawable != null)
{
foregroundShapeDrawable.ShapeAppearanceModel = shapeAppearanceModel;
}
if (compatRippleDrawable != null)
{
compatRippleDrawable.ShapeAppearanceModel = shapeAppearanceModel;
}
}
ShapeAppearanceModel getShapeAppearanceModel()
{
return shapeAppearanceModel;
}
private void updateInsetForeground(Drawable insetForeground)
{
// unsure what getForeground and setForeground is referring to here, perhaps fgDrawable?
//if (VERSION.SdkInt >= Android.OS.BuildVersionCodes.M && materialCardView.getForeground() is Android.Graphics.Drawables.InsetDrawable)
//{
// ((Android.Graphics.Drawables.InsetDrawable)materialCardView.getForeground()).setDrawable(insetForeground);
//}
//else
//{
// materialCardView.setForeground(insetDrawable(insetForeground));
//}
}
private Drawable insetDrawable(Drawable originalDrawable)
{
int insetVertical = 0;
int insetHorizontal = 0;
bool isPreLollipop = VERSION.SdkInt < Android.OS.BuildVersionCodes.Lollipop;
if (isPreLollipop || /*materialCardView.*/getUseCompatPadding())
{
// Calculate the shadow padding used by CardView
insetVertical = (int)Math.Ceil(calculateVerticalBackgroundPadding());
insetHorizontal = (int)Math.Ceil(calculateHorizontalBackgroundPadding());
}
// new custom class (see end)
return new InsetDrawable(originalDrawable, insetHorizontal, insetVertical, insetHorizontal, insetVertical);
}
private float calculateVerticalBackgroundPadding()
{
return /*materialCardView.*/getMaxCardElevation() * CARD_VIEW_SHADOW_MULTIPLIER + (shouldAddCornerPaddingOutsideCardBackground() ? calculateActualCornerPadding() : 0);
}
private float calculateHorizontalBackgroundPadding()
{
return /*materialCardView.*/getMaxCardElevation() + (shouldAddCornerPaddingOutsideCardBackground() ? calculateActualCornerPadding() : 0);
}
private bool canClipToOutline()
{
return VERSION.SdkInt >= Android.OS.BuildVersionCodes.Lollipop && bgDrawable.IsRoundRect;
}
private float getParentCardViewCalculatedCornerPadding()
{
if (/*materialCardView.*/getPreventCornerOverlap() && (VERSION.SdkInt < Android.OS.BuildVersionCodes.Lollipop || /*materialCardView.*/getUseCompatPadding()))
{
return (float)((1 - COS_45) * /*materialCardView.*/getCardViewRadius());
}
return 0f;
}
private bool shouldAddCornerPaddingInsideCardBackground()
{
return /*materialCardView.*/getPreventCornerOverlap() && !canClipToOutline();
}
private bool shouldAddCornerPaddingOutsideCardBackground()
{
return /*materialCardView.*/getPreventCornerOverlap() && canClipToOutline() && /*materialCardView.*/getUseCompatPadding();
}
private float calculateActualCornerPadding()
{
return Math.Max(
Math.Max(
calculateCornerPaddingForCornerTreatment(
shapeAppearanceModel.TopLeftCorner, bgDrawable.TopLeftCornerResolvedSize),
calculateCornerPaddingForCornerTreatment(
shapeAppearanceModel.TopRightCorner,
bgDrawable.TopRightCornerResolvedSize)),
Math.Max(
calculateCornerPaddingForCornerTreatment(
shapeAppearanceModel.BottomRightCorner,
bgDrawable.BottomRightCornerResolvedSize),
calculateCornerPaddingForCornerTreatment(
shapeAppearanceModel.BottomLeftCorner,
bgDrawable.BottomLeftCornerResolvedSize)));
}
private float calculateCornerPaddingForCornerTreatment(CornerTreatment treatment, float size)
{
if (treatment is RoundedCornerTreatment)
{
return (float)((1 - COS_45) * size);
}
else if (treatment is CutCornerTreatment)
{
return size / 2;
}
return 0;
}
private Drawable getClickableForeground()
{
if (rippleDrawable == null)
{
rippleDrawable = createForegroundRippleDrawable();
}
if (clickableForegroundDrawable == null)
{
Drawable checkedLayer = createCheckedIconLayer();
clickableForegroundDrawable = new LayerDrawable(new Drawable[] { rippleDrawable, foregroundContentDrawable, checkedLayer });
clickableForegroundDrawable.SetId(CHECKED_ICON_LAYER_INDEX, Resource.Id.mtrl_card_checked_layer_id);
}
return clickableForegroundDrawable;
}
private Drawable createForegroundRippleDrawable()
{
if (RippleUtils.UseFrameworkRipple)
{
foregroundShapeDrawable = createForegroundShapeDrawable();
return new RippleDrawable(rippleColor, null, foregroundShapeDrawable);
}
return createCompatRippleDrawable();
}
private Drawable createCompatRippleDrawable()
{
StateListDrawable rippleDrawable = new StateListDrawable();
compatRippleDrawable = createForegroundShapeDrawable();
compatRippleDrawable.FillColor = rippleColor;
rippleDrawable.AddState(new int[] { Android.Resource.Attribute.StatePressed }, compatRippleDrawable);
return rippleDrawable;
}
private void updateRippleColor()
{
if (RippleUtils.UseFrameworkRipple && rippleDrawable != null)
{
((RippleDrawable)rippleDrawable).SetColor(rippleColor);
}
else if (compatRippleDrawable != null)
{
compatRippleDrawable.FillColor = rippleColor;
}
}
private Drawable createCheckedIconLayer()
{
StateListDrawable checkedLayer = new StateListDrawable();
if (checkedIcon != null)
{
checkedLayer.AddState(CHECKED_STATE_SET, checkedIcon);
}
return checkedLayer;
}
private MaterialShapeDrawable createForegroundShapeDrawable()
{
return new MaterialShapeDrawable(shapeAppearanceModel);
}
// used in "insetDrawable" method
private class InsetDrawable : Android.Graphics.Drawables.InsetDrawable
{
public InsetDrawable(Drawable drawable, float inset) : base(drawable, inset) { }
public InsetDrawable(Drawable drawable, int inset) : base(drawable, inset) { }
public InsetDrawable(Drawable drawable, float insetLeftFraction, float insetTopFraction, float insetRightFraction, float insetBottomFraction) : base(drawable, insetLeftFraction, insetTopFraction, insetRightFraction, insetBottomFraction) { }
public InsetDrawable(Drawable drawable, int insetLeft, int insetTop, int insetRight, int insetBottom) : base(drawable, insetLeft, insetTop, insetRight, insetBottom) { }
public override int MinimumHeight => -1;
public override int MinimumWidth => -1;
public override bool GetPadding(Rect padding)
{
return false;
}
}
And usage as follows (for testing purposes):
someView.Background = new MaterialCardDrawable(context);
I know there are simpler ways to achieve the look of a CardView (using layer-list, etc), however, I specifically want to achieve the look of the MaterialCardView (as they do visually differ, in my experience). I know the MaterialCardView/MaterialCardViewHelper attempt to blend shadows with the background and other stuff which does make it look different (and different enough to be noticeable).
I am adamant on this as I am using an actual MaterialCardView just before where I intend to use this "fake" MaterialCardView. And, as such, I wish to ensure they look identical.
Why am I doing this?
I am using a RecyclerView with varying ViewHolders and one ViewHolder is a MaterialCardView (only shown once), however, the other two are not and these are the ViewHolders that are shown the most. A MaterialTextView (which acts as a title) and a bunch of Chips (which vary in number, per title).
I plan to wrap them using that MaterialCardDrawable to ensure optimal "recycling" by the RecyclerView (which wouldn't be case if I did use an actual MaterialCardView to wrap them).
What I'm trying to achieve?
Replicate the visuals of the MaterialCardView accurately, using a simple MaterialShapeDrawable to be used with RecyclerView's ItemDecoration.
I am happy for an alternative solution that can accurately replicate the visuals of the MaterialCardView, as well.
PS: I will also accept answers written in Java (it doesn't have to be written in C#).
Had a similar situation and got it working with something like this:
class CardItemDecorator(
context: Context,
#ColorInt color: Int,
#Px elevation: Float,
#Px cornerRadius: Float,
) : RecyclerView.ItemDecoration() {
private val shapeDrawable =
MaterialShapeDrawable.createWithElevationOverlay(
context,
elevation,
).apply {
fillColor = ColorStateList.valueOf(color)
shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS
setShadowColor(Color.DKGRAY)
setCornerSize(cornerRadius)
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
if (parent.childCount == 0) {
return
}
val firstChild = parent.getChildAt(0)
val lastChild = parent.getChildAt(parent.childCount - 1)
shapeDrawable.setBounds(
parent.left + parent.paddingLeft,
firstChild.top,
parent.right - parent.paddingRight,
lastChild.bottom
)
shapeDrawable.draw(c)
}
}

How to use onClickListener on two CanvasView Objects?

I am trying make a game such that when user click on shooter it changes its position and and shoots and if user click on shooted balls (or shoots as named in project) they disappear CODE HERE
Shooter.JAVA
public class shooter extends View {
//Consider all variables are declared
public shooter(int color, Context c) {
super(c);
paint = new Paint();
paint.setColor(color);
mContext = c;
}
public void move() {
//Moves Cannon On Click (CODE NOT SHOWN PURPOSELY)
invalidate();
}
public float getPosition()
{
return shootPoint;
}
public void draw(Canvas canvas)
{
super.draw(canvas);
// simply draws a rectangle (shooter)
cW=getWidth();
cH=getHeight();
center=new Point(cW/2,cH);
left=0; right=center.x; shootPoint=right/2;
canvas.drawRect(left,top,right,bottom,paint);
}
}
Another Java Class Named shoot.java is also there to make shoot balls when shootbutton is clicked
but I want that when user click on those balls(drawn on canvas ) they should reset
Main Game View Class
public class GameView extends FrameLayout implements View.OnClickListener {
//Consider all objects and variables are declared as used
public GameView(Context context, AttributeSet attrs) {
super(context,attrs);
//CONTAIN INITIALIZATION OF OBJECTS AS USED OF OTHER CLASSES
bullets = new ArrayList<shoot> ();
addView(cannon);
for (int i = 0; i < bullets.size(); i++ ) {
addView(bullets.get(i));
bullets.get(i).setOnClickListener(this);// an arrays of objects of shoot class
}
cannon.setOnClickListener(this);
level=3;level++;
}
#Override
public void onClick(View view) {
switch()// Here is the MAIN PROBLEM HOW SHOULD I DIFFERENTIATE THAT CANNON IS CLICKED OR //BULLETS LIKE USING VIEW.GETTAG()
{
case ----1:// WHAT CASE SSHOULD I WRITE
cannon.move();
break;
case ----2: // HERE ALSO
bullets.remove();
break;
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawGameBoard(canvas);
try
{
Thread.sleep(5);
} catch (InterruptedException e) {
}
invalidate();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
aliens.setBounds(0,0,width,height);
for (int i = 0; i < bullets.size(); i++ ) {
bullets.get(i).setBounds(0,0,width,height);
}
}
public void drawGameBoard(Canvas canvas) {
cannon.draw(canvas);
for ( int i = bullets.size() - 1; i >= 0; i--) {
if (bullets.get(i) != null) {
bullets.get(i).draw(canvas);
}
}
for (int i = explosions.size() - 1; i >= 0; i--) {
if (explosions.get(i) != null) {
if (!explosions.get(i).draw(canvas)) {
explosions.remove(i);
}
}
}
if (aliens != null) {
aliens.draw(canvas);
RectF guyRect = aliens.getRect();
for (int i = bullets.size() - 1; i >= 0; i--) {
if (RectF.intersects(guyRect, bullets.get(i).getRect())) {
explosions.add(new explosion(Color.RED,mContext, aliens.getX()-aliens.dst, aliens.getY()-aliens.dst));
aliens.reset();
bullets.remove(i);
break;
}
}
if (!aliens.move()) {
aliens = null;
}
}
}
// Whenever the user shoots a bullet, create a new bullet moving upwards
public void shootCannon() {
bullets.add(new shoot(Color.RED, mContext, cannon.getPosition(), (float) (height-100)));
}
}
I have showed the part of the code where I am having the problem that is the overridden function ONCLICK in GAMEVIEW.JAVA by comments like how to know what is clicked so todo their respected functions
please notify me if you didn't understand my question
If I understand the question correctly, you want to use one onClickListener function to handle the click events for both your cannon and your bullets.
Since both are different classes you may differentiate between them via '''instanceof'''.
That means your onClickListener would look something like this:
#Override
public void onClick(View view) {
if(view instanceof shooter) {
cannon.move();
}
else if(view instanceof shoot) {
bullets.remove();
}
}
I hope this helps you. If anything remains unclear I'll gladly response :)
If you want to know the coordinates of the touch which you can use to figure out, if an item is tapped,
consider using View.OnTouchListener instead of View.OnClickListener.
View.OnTouchListener has this function:
onTouch(View v, MotionEvent event)
How to use:
check if event is a tap: event.getAction() ==
MotionEvent.ACTION_DOWN
get the coordinates: event.getX() and event.getY()
And after that, replace cannon.setOnClickListener(this); with cannon.setOnTouchListener(this);
If there are any questions, do not hesitate to ask them!

How to scroll a ScrollPane in code?

I’m trying to make a scrollwheel component like this in LibGDX:
I’m using ScrollPane since it has input and fling handling built in. I have an image for the scrollwheel that is divided into 14 sections, the scrollpane itself is two sections shorter so that there will be one section on the right and left sides that it can scroll to in either direction. Once the scroll position reaches the end in either direction I want to reset the scroll position back to the center. Doing this over and over again should create the illusion of an infinite scrolling wheel (hopefully).
The problem I’m having is how to position the ScrollPane in code to reset the image once it reaches either end. So far nothing I have tried to set the scroll position has worked. I’ve tried setScrollX() and scrollTo() methods. I’ve also tried setting the size of the scrollpane to be various sizes (same size as image and two sections smaller than the image). I’ve tried calling layout, invalidate, and pack on the scrollpane to make sure it is laid out correctly before I set the scroll value. I thought that updateVisualScroll() might force it to update the scroll position, but this also has no effect.
No matter what I do it simply ignores all of my calls to change the scroll position so I’m clearly missing something. In my code below I'm trying to get the scrollwheel to start in the center of the image and instead it's starting position is all the way at the left.
I also need to be able to get the current scroll position to detect when it has reached either end. I tried overriding the act() method and printing out scrollPane.getX(), but this value was always “0” even when I manually clicked and dragged it to scroll the ScrollPane.
The scrolling does work when manually clicking and dragging, so I believe the ScrollPane is set up correctly, I just can’t get it to scroll within the code.
Here is my code, and for simplicity I took all of my experimentation code out because none of my experimenting worked.
public class MyScrollWheel extends Container<ScrollPane> {
private ScrollPane scrollPane;
private Image image;
private int scrollOffset;
public MyScrollWheel(){
Texture texture = new Texture(Gdx.files.internal("internal/scrollwheel.png"));
image = new Image(texture);
scrollOffset = (int)(image.getWidth()/14);
scrollPane = new ScrollPane(image);
scrollPane.setOverscroll(false, false);
setActor(scrollPane);
size(image.getWidth()-(scrollOffset*2), image.getHeight());
scrollPane.setScrollX(scrollOffset); // << this doesn't scroll
scrollPane.updateVisualScroll();
}
}
Well, I hopefully managed to get something you can build upon. What I simply did was extending actor and have it accept a Texture so I could use Texture.wrap and have it draw with SpriteBatch.draw(). I am able to keep scrolling it now and based on the scroll delta you can figure out how far it has been scrolled. I don't see any need to reset the wheel but if you really want to you can just do wheel.setScroll(0);.
One limitation is that it is not a Drawable so it cannot be scaled like a NinePatch. You have to give it a plain wheel texture draw it the size you want it to spear, you can add normal scaling however and keep aspect ratio manually. Then add the sides to it and perhaps overlay a gradient on those to create depth.
ScrollWheel:
public class ScrollWheel extends Actor {
Texture wheelTexture;
private int scroll = 0;
public int getScroll() {
return scroll;
}
public void setScroll(int scroll) {
this.scroll = scroll;
}
public ScrollWheel(Texture texture)
{
wheelTexture = texture;
wheelTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.ClampToEdge);
setWidth(texture.getWidth());
setHeight(texture.getHeight());
}
#Override
public void draw(Batch batch, float parentAlpha) {
super.draw(batch, parentAlpha);
batch.draw(wheelTexture, getX(), getY(), scroll, 0,
wheelTexture.getWidth(), wheelTexture.getHeight());
}
}
usage in a Screen:
public class TestScreen implements Screen {
Stage stage;
ScrollWheel wheel;
public TestScreen() {
stage = new Stage();
Table t = new Table();
t.setFillParent(true);
stage.addActor(t);
wheel = new ScrollWheel(new Texture("hud/wheel_part.png"));
wheel.addListener(new DragListener() {
#Override
public void drag(InputEvent event, float x, float y, int pointer) {
super.drag(event, x, y, pointer);
wheel.setScroll(wheel.getScroll() + (int)getDeltaX());
}
});
t.add(wheel);
Gdx.input.setInputProcessor(stage);
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(.3f, .36f, .42f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act();
stage.draw();
}
//...Other mandatory screen methods...
}
So just create a wheel texture that is tillable and include that with the ScrollWheel constructor. It will draw the wheel in the center of the screen if you use this exact code.
The scroll variable essentially holds the amount of scroll so if you you want limit this between 0 and 100 you would just add this functionality in setScroll().
if (scroll > 100) scroll = 100;
else if (scroll < 0) scroll = 0;
You could then add a step to it. So if you want to rotate a image with the slider you could set the rotation by scroll * 3,6f or scroll * (maxScroll / maxStep)
I really liked the way this turned out, I will be using this for my slider in the future :D. I have extended and altered it a bit already and you can see my implementation here: https://youtu.be/RNLk5B-VfYg
Expanding on Menno Gouw's scroll wheel, I've added some more features:
Fling support with setting for fling time
Precision setting to adjust sensitivity of the wheel
Takes a Drawable
Compatible for use inside of a ScrollPane
NOTE: For my purposes I have it take in a Label in the constructor, but this can easily be changed if you don't want it tied to a Label.
Here is a video I recorded on phone demoing the scroll wheel.
https://www.youtube.com/watch?v=RwVrez4BZsY&feature=youtu.be
- EDIT 1: Layout bugs have now been fixed (hopefully). It now updates its position when moved in a ScrollPane and the Drawables are clipped to the border of the Actor.
- EDIT 2: Added support for a stationary drawable for shading and a method to change the wheel's direction (setRightPositiveDirection()).
public class ScrollWheel extends Actor {
private Drawable wheelDrawable, wheelShading;
private Label label;
private int unscaledScrollValueX=0, scrollValueX=0;
private boolean isNotEdge;
private int precision=40;
private int direction=1;
private int minValue=Integer.MIN_VALUE, maxValue=Integer.MAX_VALUE;
// MANUAL SCROLL
private int separator;
private int wheelWidth;
// FLING
private float flingTimer, flingTime=1f;
private float velocityX;
public ScrollWheel(Drawable wheelDrawable, Drawable wheelShading, Label label) {
this.wheelDrawable = wheelDrawable;
this.wheelShading = wheelShading;
this.label = label;
wheelWidth = (int)wheelDrawable.getMinWidth();
separator = wheelWidth;
setWidth(wheelDrawable.getMinWidth());
setHeight(wheelDrawable.getMinHeight());
// stops ScrollPane from overriding input events
InputListener stopTouchDown = new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
event.stop();
return false;
}
};
addListener(stopTouchDown);
ActorGestureListener flickScrollListener = new ActorGestureListener() {
public void pan (InputEvent event, float x, float y, float deltaX, float deltaY) {
updateScroll(deltaX);
}
public void fling (InputEvent event, float x, float y, int button) {
if (Math.abs(x) > 150) {
flingTimer = flingTime;
velocityX = x;
}
}
public boolean handle (Event event) {
if (super.handle(event)) {
if (((InputEvent)event).getType() == InputEvent.Type.touchDown) flingTimer = 0;
return true;
}
return false;
}
};
addListener(flickScrollListener);
}
private void updateScroll(float delta){
unscaledScrollValueX += (delta * direction);
scrollValueX = (int)(unscaledScrollValueX / precision);
isNotEdge = true;
if (scrollValueX <= minValue){
scrollValueX = minValue;
unscaledScrollValueX = minValue * precision;
isNotEdge = false;
}
else if (scrollValueX >= maxValue){
scrollValueX = maxValue;
unscaledScrollValueX = maxValue * precision;
isNotEdge = false;
}
if (isNotEdge){
separator += delta;
if (separator <= 0){
separator = wheelWidth;
}
else if (separator >= wheelWidth) {
separator = 0;
}
}
updateLabel();
}
private void updateLabel(){
label.setText("" + scrollValueX);
}
public void setMinValue(int minValue){ this.minValue = minValue; }
public void setMinValueToNone(){ minValue=Integer.MIN_VALUE; }
public void setMaxValue(int maxValue){ this.maxValue = maxValue; }
public void setMaxValueToNone(){ minValue=Integer.MAX_VALUE; }
public void setFlingTime(float flingTime){ this.flingTime = flingTime; }
public void setPrecision(int precision){ this.precision = precision; }
public void setRightPositiveDirection(boolean rightPositive){ direction = (rightPositive) ? 1 : -1; }
#Override
public void act(float delta){
super.act(delta);
boolean animating = false;
if (flingTimer > 0) {
float alpha = flingTimer / flingTime;
updateScroll(velocityX * alpha * delta);
flingTimer -= delta;
if (flingTimer <= 0) {
velocityX = 0;
}
animating = true;
}
if (animating) {
Stage stage = getStage();
if (stage != null && stage.getActionsRequestRendering()){
Gdx.graphics.requestRendering();
}
}
}
#Override
public void draw(Batch batch, float parentAlpha){
super.draw(batch, parentAlpha);
batch.flush();
if (clipBegin(getX(), getY(), getWidth(), getHeight())){
wheelDrawable.draw(batch, getX() + separator - wheelWidth, getY(), wheelDrawable.getMinWidth(), wheelDrawable.getMinHeight());
wheelDrawable.draw(batch, getX() + separator, getY(), wheelDrawable.getMinWidth(), wheelDrawable.getMinHeight());
wheelShading.draw(batch, getX(), getY(), wheelShading.getMinWidth(), wheelShading.getMinHeight());
batch.flush();
clipEnd();
}
}
}

How to get (x,y) cordinates of image in android?

I have drawn a image on ONTOUCH with (X,Y)cordinates while moving the image it's cordinates should move along with the image when it reaches it end point the image should be drawn there or else the image should go back to it's starting position.
For eg:(in our mobile if kepad lock we will drag it to open if it reaches it end point the lock will open or else the image will reaches it's starting position).
Reffered link:http://www.vogella.com/tutorials/AndroidTouch/article.html
Here is my code:
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mLocked) {
return false;
}
final int action = event.getAction();
float x = event.getX();
float y = event.getY();
if (mOnDrawerScrollListener != null) {
mOnDrawerScrollListener.onScrollStarted();
}
final int pt = getSide();
mTouchDelta = (int)(y - pt);
prepareTracking(pt);
mVelocityTracker.addMovement(event);
final Rect frame = mFrame;
final View handle = mHandle;
If anyone have idea about this please help me friends.
If you want to get(x, y) cordinates of view (ImageView), just use view.getX(), view.getY(). But when you get it in onCreate(), the results will be (0, 0), because it is not created yet. You should use view.getX(), view.getY() in onGlobalLayoutListener(). For e.g:
rlRoot.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
rlRoot.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Log.d("X", view.getX()+"");
Log.d("Y", view.getY()+"");
}
});
With rlRoot is the layout includes your view and view may be ImageView or any View.
Try it, hope it help.

Android - View.requestLayout doesn't work in OnLayoutChangeListener

I have to use the GridLayout for my application. The problem of GridLayout is the limitation of weight so I must scale its children size at runtime. I do this by the help of an OnGlobalLayoutListener.
The children of my GridLayout are two Buttons which have got the width of the parent and the half of parents height. One Button above and one Button below.
If the upper Button is clicked I want to switch the size of the GridLayout to 500 in width and height and 700 in width and height.
After the click at the upper Button the Buttons should scale correctly but they don't do that.
public class HauptAktivitaet extends Activity implements OnLayoutChangeListener, OnClickListener{
/** ContentView its sizes I will change on a click later */
private GridLayout mGridLayout;
/** LayoutParams of the above child */
private GridLayout.LayoutParams mGridLayout_LayoutParams_Above;
/** LayoutParams of the below child */
private GridLayout.LayoutParams mGridLayout_LayoutParams_Below;
/** Children of the ContentView */
private Button mButtonAbove;
private Button mButtonBelow;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
inline();
setContentView(mGridLayout);
}
private void inline(){
/* instantiation of ContentView which is named mGridLayout */
mGridLayout = new GridLayout(this);
/* set the count of rows and columns of ContentView */
mGridLayout.setRowCount(2);
mGridLayout.setColumnCount(1);
/* set OnGlobalLayoutListener for observe a change of its layout */
mGridLayout.addOnLayoutChangeListener(this);
/* instantiation of the LayoutParams for the children */
mGridLayout_LayoutParams_Above = new GridLayout.LayoutParams(GridLayout.spec(0), GridLayout.spec(0));
mGridLayout_LayoutParams_Below = new GridLayout.LayoutParams(GridLayout.spec(1), GridLayout.spec(0));
/* instantiation of the children and setting of their LayoutParams */
mButtonAbove = new Button(this);
mButtonAbove.setLayoutParams(mGridLayout_LayoutParams_Above);
mButtonAbove.setOnClickListener(this); // A click on this Button changes the Size of its parent ViewGroup.
/* instantiation of ContentView */
mButtonBelow = new Button(this);
mButtonBelow.setLayoutParams(mGridLayout_LayoutParams_Below);
/* add children to the ContentView */
mGridLayout.addView(mButtonAbove);
mGridLayout.addView(mButtonBelow);
}
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
/* Width and height on this time are known */
int width = mGridLayout.getWidth();
int height = mGridLayout.getHeight();
/* Changes the LayoutParams of ContentViews children dynamicly*/
mGridLayout_LayoutParams_Above.width = width;
mGridLayout_LayoutParams_Above.height = height / 2;
mGridLayout_LayoutParams_Below.width = width;
mGridLayout_LayoutParams_Below.height = height / 2;
/* ISSUE:
* should update the rendering of the buttons but it doesn't work correctly */
mButtonAbove.requestLayout();
mButtonBelow.requestLayout();
/* A little debug info for knowing of the ContentViews size */
mButtonBelow.setText("Own Width = " + mButtonBelow.getWidth() + "\n" +
"Own Height = " + mButtonBelow.getHeight() + "\n" +
"Perents Width = " + width + "\n" +
"Perents Height = " + height);
}
private boolean switcher = true;
/**
* Works correctly
* */
#Override
public void onClick(View v) {
int width = 0;
int height = 0;
if(switcher){
width = 500;
height = 500;
switcher = false;
}else{
width = 700;
height = 700;
switcher = true;
}
mGridLayout.getLayoutParams().width = width;
mGridLayout.getLayoutParams().height = height;
mGridLayout.requestLayout();
}
}
Can anybody tell me what to do?
How can I tell the ContentView to refresh itself and its children? I am searching for an elegant solution.
Thank you very much for answers!
Regards! :-)
I ran into the same problem. I guess Android has not finished calculating the new layout when onLayoutChanges() is called, and a recalculation cannot be triggered from within the method.
The solution is to use a Handler to post layout changes to the end of the UI thread.
I am not really sure about requestLayout(), maybe replace it with another call of setLayoutParams(). It probably has the same effect.
So implement your re-layout in a class implementing Runnable, instantiate a Handler in onCreate() and in onLayoutChanges() instantiate the Runnable class and post it to the Handler:
public class HauptAktivitaet extends Activity implements OnLayoutChangeListener, OnClickListener{
// this class updates the layout
private class LayoutUpdater implements Runnable {
private final int width;
private final int height;
private LayoutUpdater(int width, int height) {
this.width = width;
this.height = height;
}
public void run() {
mGridLayout_LayoutParams_Above.width = width;
mGridLayout_LayoutParams_Above.height = height;
mGridLayout_LayoutParams_Below.width = width;
mGridLayout_LayoutParams_Below.height = height;
// might be necessary or not: set layout parameters to trigger update
mButtonAbove.setLayoutParams(mGridLayout_LayoutParams_Above);
mButtonBelow.setLayoutParams(mGridLayout_LayoutParams_Below);
}
}
/* snip */
// add handler running in UI thread
private Handler uiHandler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// instantiate handler
uiHandler = new Handler();
inline();
setContentView(mGridLayout);
}
/* snip */
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
/* Width and height on this time are known */
int width = mGridLayout.getWidth();
int height = mGridLayout.getHeight();
// post layout update to the end of queue
uiHandler.post(new LayoutUpdater(width, height / 2);
}
}
#user3492470
Thank you so much for your answer. I thought nobody has an idea.
Your “solution” is interesting but unfortunately a little bit wrong. Optically it works great but in the background you have got an endless loop …
You will see it if you set a counter at the text of one of the Buttons. The counter increases endlessly.
The setting of Buttons' new layout size in the Handler is followed by a new call of the OnLayoutChangeListener and this Listener starts the Handler again and so on …
You will get the same problem if you are using the OnGlobalLayoutListener instead of the OnLayoutChangeListener with a Handler.
The reason is the following. The view.requestLayout() or view.setLayoutParams(…) does not work in the onLayoutChange method, but it works in the onGlobalLayout method or in a Handler which creates a new thread.
Now the part I really don't understand:
A successful call on the child.requestLayout() makes a new call on the parent's onLayoutChange method. This is peculiar because the parent's layout should not change again.
However that be, a little if/else logic solves the endless calls.
private int counter = 0;
#Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
/* Width and height on this time are known */
int width = mGridLayout.getWidth();
int height = mGridLayout.getHeight();
/* Buttons width and height are supposed to be */
int mButtons_width = width;
int mButtons_height = height / 2;
if(!(mButtonAbove.getWidth() == mButtons_width &&
mButtonAbove.getHeight() == mButtons_height &&
mButtonBelow.getWidth() == mButtons_width &&
mButtonBelow.getHeight() == mButtons_height)
){
/* Changes the LayoutParams of ContentViews children dynamicly */
mGridLayout_LayoutParams_Above.width = mButtons_width;
mGridLayout_LayoutParams_Above.height = mButtons_height;
mGridLayout_LayoutParams_Below.width = mButtons_width;
mGridLayout_LayoutParams_Below.height = mButtons_height;
/* NO ISSUE ANYMORE:
* updates the rendering of the buttons */
mButtonAbove.requestLayout();
mButtonBelow.requestLayout();
}
/* A little debug info for knowing the ContentView's and Children's size */
mButtonBelow.setText("Own Width = " + mButtonBelow.getWidth() + "\n" +
"Own Height = " + mButtonBelow.getHeight() + "\n" +
"Perents Width = " + width + "\n" +
"Perents Height = " + height + "\n" +
"Calls = " + counter++);
}
In this case I use the OnGlobalLayoutListener instead of the OnLayoutChangeListener/uiHandler Combination. ;-) The if/else logic is the same.
My original question, why requestLayout does not work in onLayoutChange, is still open but a satisfying solution was presented here so I am happy.
Thank you again for your concern!
handler is dangerous.suggest use view.getViewTreeObserver().addOnPreDrawListener();
this is not conflict;

Categories