Error: The specified child already has a parent - java

In my program I get this error on my emulator. If I run this on my Razr MAXX it works? It says that
10-28 19:38:27.935: E/AndroidRuntime(2268): java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
The error occurs on this line: layout.addView(mid)
I believe it says that mid already has a parent, so I debugged with ViewGroup parent = (ViewGroup) mid.getParent(); and it returns null...
Can someone please lead me in the right direction?
Below is my class:
package com.example.draganddroptest;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* #author Brandon Ling
*
*/
public class HoneycombView extends View {
private Context context;
private ViewGroup layout;
private HexagonView top;
private HexagonView topRight;
private HexagonView topLeft;
private HexagonView bot;
private HexagonView botLeft;
private HexagonView botRight;
private HexagonView mid;
private int radius;
private PointF shift;
private String color;
private String[] colors;
private int strokeWidth;
Paint paint = new Paint();
/**
* #param context
* #param layout
*/
public HoneycombView(Context context, AttributeSet attrs) {
this(context, null, 0, null, null, attrs, 0);
}
public HoneycombView(Context context, ViewGroup layout, int radius, PointF shift, String color, AttributeSet attrs, int strokeWidth) {
super(context);
this.context = context;
if (attrs != null) { // probably sent via xml
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HoneycombView);
this.radius = a.getInt(R.styleable.HoneycombView_radius, this.strokeWidth);
this.shift = new PointF(a.getInt(R.styleable.HoneycombView_shiftX, 0), a.getInt(
R.styleable.HoneycombView_shiftY, 0));
this.colors = a.getString(R.styleable.HoneycombView_hexColors).split("\\|");
a.recycle();
} else {
this.layout = layout;
this.radius = radius;
this.shift = shift;
this.color = color;
}
this.mid = new HexagonView(this.context, null, this.radius, this.shift, this.colors[0], 10);
this.top = mid.addHexTop(colors[1]);
this.topRight = mid.addHexTopRight(colors[2]);
this.topLeft = mid.addHexTopLeft(colors[3]);
this.bot = mid.addHexBot(colors[4]);
this.botRight = mid.addHexBotRight(colors[5]);
this.botLeft = mid.addHexBotLeft(colors[6]);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.layout = (ViewGroup) this.getParent();
ViewGroup parent = (ViewGroup) mid.getParent();
//parent.removeAllViews();
layout.addView(mid);
layout.addView(top);
layout.addView(topRight);
layout.addView(topLeft);
layout.addView(bot);
layout.addView(botRight);
layout.addView(botLeft);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = (int) Math.ceil(this.bot.getMaxY()) + this.strokeWidth;
int width = (int) Math.ceil(this.botRight.getMaxX()) + this.strokeWidth;
setMeasuredDimension(width, height);
}
}

onDraw() is called repeatedly by Android. The second call to onDraw() will throw this exception because each View is already added to the parent.
That said, as a rule, you never change the layout inside onDraw(). This method is for painting on the Canvas only. Even if you manage to fix this exception by calling removeAllViews() (as it looks like you might have tried), you will force Android to continuously measure and draw the layout again and again as views are removed and re-added.
You probably don't actually want to add your child views to the parent. If you want to add child views, you should add them as children of the HoneyCombView. To do this, you'll need to extend ViewGroup instead of View. And again, add the child views somewhere else, not in onDraw().
Although adding child views to a ViewGroup will work, it's not very good for performance. Each child view you add to a layout slows the application. (See Use Fewer Views.) A much better alternative is to draw shapes directly onto the Canvas using (for example) canvas.drawPath().

Related

Is there a way to restart marquee/scrolling when the last letter of the text becomes visible?

I want a text inside a textbox to scroll to end and then restart.
Using marquee scrolls the whole text until the first letter reaches the "start point", but I want the text only to scroll until the last letter of the text becomes visible, then pause and the text should jump to start and start scrolling again.
Is there a way to do this. I have search but I can't find something that works for me.
Thanks,
Sebi
I am not aware of any way to configure a TextView to take on the marquee behavior that you are seeking. The Stack Overflow Q/A I referred you to in the comments does translate the entire TextView. I think that this will work as a marquee if you set the TextView within a ViewGroup such that the TextView is clipped. You can also do you own clipping. You would have to try this to see if it would work. If this does work, it may be sufficient for your needs, but you may be missing the fading edge and maybe some other characteristics of true marquee scrolling. Also, some other characteristics of the TextView such as shadows, borders and backgrounds might not look right.
The contents of the TextView is animated by the TextView code itself through canvas translation for marquee scrolling. See here.
if (isMarqueeFadeEnabled()) {
if (!mSingleLine && getLineCount() == 1 && canMarquee()
&& (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
final int width = mRight - mLeft;
final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
final float dx = mLayout.getLineRight(0) - (width - padding);
canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
}
if (mMarquee != null && mMarquee.isRunning()) {
final float dx = -mMarquee.getScroll();
canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
}
}
You can also consider writing a custom TextView and doing your own text scrolling. You can look to the TextView code itself for some tips on how you might do it. I think writing your own TextView would have better results. (IMO)
I have rewrite the Code from the GitHub Project here (which was posted in the comment from Cheticamp) to java and shorten it to my needs. Now it starts the animation infinite.
Here the java code:
MarqueeTextView.java:
public class MarqueeTextView extends androidx.appcompat.widget.AppCompatTextView {
private final Scroller mScroller;
private int mDelay;
private int mDuration;
private final Handler mHandler = new Handler();
private final Runnable mAnimateRunnable = new Runnable() {
#Override
public void run() {
//check if Animation is needed otherwise check next cycle (if text has changed for example)
if (getTextViewWidthWithPadding() < getTextWidth()) {
mScroller.startScroll(getStartX(), 0, 0, 0, 0);
invalidate();
int direction = getLayout().getParagraphDirection(0);
mScroller.startScroll(getStartX(), 0, (getTextWidth() - getTextViewWidthWithPadding()) * direction, 0, mDuration);
}
mHandler.postDelayed(mAnimateRunnable, mDelay);
}
};
public MarqueeTextView(Context context) {
this(context, null);
}
public MarqueeTextView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
mScroller = new Scroller(context, new LinearInterpolator());
setScroller(mScroller);
}
private int getTextWidth() {
String text = getText().toString();
TextPaint paint = getPaint();
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
return bounds.width();
}
private int getTextViewWidthWithPadding() {
return getWidth() - (getPaddingStart() + getPaddingEnd());
}
private int getStartX() {
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
int lineRight = (int) getLayout().getLineRight(0);
if (isRtl) {
return lineRight - getTextViewWidthWithPadding();
} else {
return 0;
}
}
public void startAnimation(int delay, int duration) {
mDelay = delay;
mDuration = duration;
mHandler.postDelayed(mAnimateRunnable, delay);
}
}
activity_main.xml:
<MarqueeTextView
android:id="#+id/name"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:ellipsize="none"
android:singleLine="true"
android:text="1. Simple text that shows how to use custom marquee"
/>
MainActivty.java:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((MarqueeTextView)findViewById(R.id.name)).startAnimation(3000, 2000);
}

HorizontalScrollView scrollTo doesn't scroll above original width of the view

I have a custom view containing an HorizontalScrollView. The width of the scroll view is match_parent, and the width of its children is initially programmatically set based on the value of an attribute of the custom view. At some point, the width of the children of the scroll view are updated (increased) programmatically. The problem is that, after the update, the scrollTo method is still unable to scroll above the original width value (the same for scrollBy).
The enclosing view (the custom one) have a left and right padding equal to half the screen, if this is relevant.
Example:
Initial HorizontalScrollView's children width: 1000;
HorizontalScrollView's parent width: 1080;
HorizontalScrollView's left/right padding = 540;
New HorizontalScrollView's children width: 2000;
scrollTo(1100, 0) = scrollTo(1000, 0); <---- here is the problem
Relevant code:
Custom view initialization:
private void init(AttributeSet attrs, int defStyle) {
inflate(getContext(), R.layout.timeline_view, this);
// Load attributes
final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TimelineView, defStyle, 0);
m_timelineInitialLength = a.getDimension(R.styleable.TimelineView_timelineInitialLength, DEFAULT_LENGTH);
a.recycle();
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
LinearLayout timelineWidget = findViewById(R.id.timeline_widget);
int horizontalPadding = displayMetrics.widthPixels / 2;
timelineWidget.setPadding(horizontalPadding, 0, horizontalPadding, 0);
m_horizontalScrollView = findViewById(R.id.scroll_view);
m_horizontalScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
m_currentScrollX = m_horizontalScrollView.getScrollX();
}
});
m_currentScrollX = m_horizontalScrollView.getScrollX();
update(m_timelineInitialLength, m_timelineInitialStepWidth, m_currentScrollX);
}
Update and scrollTo (this method is called also at the end of the initialization. m_rulerView and m_timelineLayout are the children of the HorizontalScrollView):
private void update(float timelineLength, float timelineStepWidth, int currentScrollX) {
m_rulerView = findViewById(R.id.ruler);
m_rulerView.setIndicator(null);
m_rulerView.getLayoutParams().width = (int) timelineLength;
m_rulerView.requestLayout();
m_timelineLayout = findViewById(R.id.timeline);
m_timelineLayout.getLayoutParams().width = (int) timelineLength;
m_timelineLayout.requestLayout();
m_horizontalScrollView.scrollTo(currentScrollX, 0);
}
The problem seems to be that the scrolling happens before the children have been redrawn, before calling requestLayout only schedule the update of the view to be executed at some point in the future (the same for invalidate()).
I solved using a workaround, by executing scrollTo after some time:
new Handler().postDelayed(() -> ((Activity) getContext()).runOnUiThread(() -> m_horizontalScrollView.scrollTo(currentScrollX, 0)), 200);
I'm still open to better solutions.

How to make auto smooth scroll with offset Recycler View?

There is such issue, I have horizontal RecyclerView where each cell less than width of screen.
So I found a solution here
RecyclerVIew auto scroll to display all the elements as in News Feed etc.,
All work excellent if one cell take whole width of the screen otherwise(if each cell take 95% of screen width) every auto swipe place the cell at the beginner of screen (right side) and it is logical. So at the end of one visible cell it is start of another cell
it is doesn't looks good. I need this cell to be at the middle of the screen like this.
I need to see previous cell - current - next one
Now I would like to explain some magic how I make current smooth scroll (as I mentioned at link above)
this method in my CustomLinnearLayoutManager
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position)
{
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext())
{
#Override
public PointF computeScrollVectorForPosition(int targetPosition)
{
return SmoothLayoutManager.this.computeScrollVectorForPosition(targetPosition);
}
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics)
{
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
But this method works without offset
I found out one more method that can provide desired offset
scrollToPositionWithOffset(final int position, final int offset)
And it is looks like exactly what I need , but this method works without smooth animation.
So, eventually my question is : how to apply animation logic from first method to second (that with offset)
Feel free to ask
To auto snapping and showing one item at center of RecyclerView, simply you need to use LinearSnapHelper like following:
LinearSnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);
If you want to scroll to a specific item programmatically, LinearSnapHelper handles snapping functionality too.
SmoothScroller smoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
#Override
protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_ANY;
}
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 120f / displayMetrics.densityDpi;
}
};
...
smoothScroller.setTargetPosition(position);
recyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
Here is the visual result:
.
..................Manually Scrolling...........................Programmatically Scrolling..........
Eventually, I found the way thanks a lot to #aminography for his answer and also one more answer help me a lot
https://stackoverflow.com/a/39654328
Actually now I have such implementation
My custom LinnearLayoutManager implementation
public class SmoothLayoutManager extends LinearLayoutManager
{
public static final int X_25 = 25;
public static final int X_200 = 200;
public static final float DEFAULT = X_25;
/**
* !! IMPORTANT !!
* If you need to add new value, don't forget add it here also
*/
#Retention(RetentionPolicy.SOURCE)
#IntDef({X_25, X_200})
private #interface Speed
{
}
private static float MILLISECONDS_PER_INCH = DEFAULT;
public SmoothLayoutManager(Context context)
{
super(context);
}
public SmoothLayoutManager(Context context, int orientation, boolean reverseLayout)
{
super(context, orientation, reverseLayout);
}
public SmoothLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
public SmoothLayoutManager setSpeedOfSmooth(#Speed int iSpeed)
{
MILLISECONDS_PER_INCH = iSpeed;
return this;
}
#Override
public void scrollToPositionWithOffset(final int position, final int offset)
{
super.scrollToPositionWithOffset(position, offset);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position)
{
RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(recyclerView.getContext())
{
#Override
public PointF computeScrollVectorForPosition(int targetPosition)
{
return SmoothLayoutManager.this.computeScrollVectorForPosition(targetPosition);
}
#Override
protected int getVerticalSnapPreference()
{
return LinearSmoothScroller.SNAP_TO_ANY;
}
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics)
{
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
#Override
public int calculateDtToFit(final int viewStart, final int viewEnd, final int boxStart, final int boxEnd, final int snapPreference)
{
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
}
And this is how I make set
private void setRv(Context iC)
{
RecyclerView.Adapter adapter = new UpSaleInnerAdapter(mPicasso, mInflater, iLink -> mListener.onButtonClick(iLink));
mRv.setLayoutManager(new SmoothLayoutManager(iC, LinearLayoutManager.HORIZONTAL, false).setSpeedOfSmooth(SmoothLayoutManager.X_200));
mRv.setAdapter(adapter);
SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(mRv);
}
Note :
I noticed that sometimes if you make fast swipe, so SnapHelper a little bit confused and pass more cells that need... like a turbo mode :)
If someone will find how to fix it, let me know.
Thanks!

Why can't I use a mask filter with my ImageView?

I am using a glow effect which works using setMaskFilter to blur the painted area:
public static Paint createGlowPaint(Context context, #ColorRes int baseColorKey) {
float glowWidth = context.getResources().getDimension(R.dimen.analog_blur_width);
Paint paint = new Paint();
paint.setColor((Workarounds.getColor(context, baseColorKey) & 0xFFFFFF) | 0x40000000);
paint.setMaskFilter(new BlurMaskFilter(glowWidth, BlurMaskFilter.Blur.NORMAL));
paint.setStrokeWidth(glowWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
return paint;
}
This is used along with custom vector graphics to do the drawing of the blur and then the drawing of the hands of my watch face. This is all packaged up into a Drawable so that I can use it in more than one place. Or so I thought! It turns out that even though this works fine when painted directly onto the top-level canvas, when I try to use ImageView#setImageDrawable to set the exact same Drawable onto an ImageView, the filter no longer gets applied.
You can see how this sort of blur looks:
Using it with an ImageView, now you get a hard edge instead:
What is going on here?
Edit for additional info:
Code that does the drawing, i.e. is using the glow paint:
public abstract class Hands extends Drawable {
// ... lots of other cruft
#Override
public void draw(Canvas canvas) {
//todo could probably precompute more in onBoundsChange
Rect bounds = getBounds();
int centerX = bounds.centerX();
int centerY = bounds.centerY();
if (watchMode.isInteractive()) {
handGlowPath.reset();
handGlowPath.addPath(hourHand.getPath());
handGlowPath.addPath(minuteHand.getPath());
handGlowPath.addCircle(centerX, centerY, centreRadius, Path.Direction.CCW);
canvas.drawPath(handGlowPath, handGlowPaint);
}
hourHand.draw(canvas);
minuteHand.draw(canvas);
if (watchMode.isInteractive()) {
secondHand.draw(canvas);
if (hasThirds()) {
thirdHand.draw(canvas);
}
}
canvas.drawCircle(centerX, centerY, centreRadius, centrePaint.getPaint());
}
Code that puts the drawable into the ImageView:
class PreviewListViewAdapter extends BaseAdapter implements ListAdapter {
// ... other methods for the list adapter
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView instanceof ViewHolder) {
holder = (ViewHolder) convertView;
} else {
holder = new ViewHolder(getContext(), inflater.inflate(R.layout.config_list_item, parent, false));
}
// Deliberately making this a square.
LinearLayout.LayoutParams holderLayoutParams =
new LinearLayout.LayoutParams(parent.getWidth(), parent.getWidth());
LinearLayout.LayoutParams viewLayoutParams =
new LinearLayout.LayoutParams(parent.getWidth(), parent.getWidth());
if (position == 0) {
holderLayoutParams.height += 20 * getResources().getDisplayMetrics().density;
viewLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
} else if (position == getCount() - 1) {
holderLayoutParams.height += 20 * getResources().getDisplayMetrics().density;
viewLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
} else {
viewLayoutParams.gravity = Gravity.CENTER;
}
holder.setLayoutParams(holderLayoutParams);
holder.view.setLayoutParams(viewLayoutParams);
holder.image.setImageDrawable(items[position].drawable);
holder.text.setText(items[position].labelText);
return holder;
}
}
Starting from API 14, BlurMaskFilters are not supported when hardware acceleration is enabled.
To work around this, set your ImageView's layer type to software:
holder.image.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Android image view top crop

I'm developing an android application and i need a header to use at the top of the screen similar to an action bar ..... i want it to be transparent but when you start to scroll the view all the content of the view goes behind it so i decided to use an image view (it has to be different in any fragment that's why i didn't use action bar) so i used a frame layout and fixed the image view at the top with the height of 50 and i used the same background of the main view as the source of the image view .... my problem is the scale type i used Center Crop in the main view and it's perfect but it's useless in the header so i'm looking for some thing like this :
which is exactly like center crop but crops from the top of the image .
i used this link but it wasn't what i'm looking for .....
Top Crop ImageView:
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.widget.ImageView;
public class TopCropImageView extends ImageView {
private Matrix mMatrix;
private boolean mHasFrame;
#SuppressWarnings("UnusedDeclaration")
public TopCropImageView(Context context) {
this(context, null, 0);
}
#SuppressWarnings("UnusedDeclaration")
public TopCropImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
#SuppressWarnings("UnusedDeclaration")
public TopCropImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mHasFrame = false;
mMatrix = new Matrix();
// we have to use own matrix because:
// ImageView.setImageMatrix(Matrix matrix) will not call
// configureBounds(); invalidate(); because we will operate on ImageView object
}
#Override
protected boolean setFrame(int l, int t, int r, int b)
{
boolean changed = super.setFrame(l, t, r, b);
if (changed) {
mHasFrame = true;
// we do not want to call this method if nothing changed
setupScaleMatrix(r-l, b-t);
}
return changed;
}
private void setupScaleMatrix(int width, int height) {
if (!mHasFrame) {
// we have to ensure that we already have frame
// called and have width and height
return;
}
final Drawable drawable = getDrawable();
if (drawable == null) {
// we have to check if drawable is null because
// when not initialized at startup drawable we can
// rise NullPointerException
return;
}
Matrix matrix = mMatrix;
final int intrinsicWidth = drawable.getIntrinsicWidth();
final int intrinsicHeight = drawable.getIntrinsicHeight();
float factorWidth = width/(float) intrinsicWidth;
float factorHeight = height/(float) intrinsicHeight;
float factor = Math.max(factorHeight, factorWidth);
// there magic happen and can be adjusted to current
// needs
matrix.setTranslate(-intrinsicWidth/2.0f, 0);
matrix.postScale(factor, factor, 0, 0);
matrix.postTranslate(width/2.0f, 0);
setImageMatrix(matrix);
}
#Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
// We have to recalculate image after chaning image
setupScaleMatrix(getWidth(), getHeight());
}
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
// We have to recalculate image after chaning image
setupScaleMatrix(getWidth(), getHeight());
}
#Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
// We have to recalculate image after chaning image
setupScaleMatrix(getWidth(), getHeight());
}
// We do not have to overide setImageBitmap because it calls
// setImageDrawable method
}
I managed to get this working with the PhotoView library that can be found here:
https://github.com/chrisbanes/PhotoView
All you need to do is set the library up and on the image view you are using set the scale type to CENTER_CROP. Then in the library go to the PhotoViewAttacher.java file and under the method updateBaseMatrix() where the code for CENTER_CROP is, change it to the following:
else if (mScaleType == ScaleType.CENTER_CROP) {
float scale = Math.max(widthScale, heightScale);
mBaseMatrix.postScale(scale, scale);
//Changed dy = 0 for top crop
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
0);
Basically, you set dy=0 so that the matrix stays at the top of the image on the y axis and doesn't center it. And the CENTER_CROP scale type will behave like a TOP_CROP
Your approach is kind of hacky, I suggest you take a look at using a Sticky Fragment (for example).
The Video, Source and Sample can be found in this Google+ Post

Categories