Okay, here is what I am doing... I have two custom layouts:
HandedLayout extends ViewGroup
and
HandedPathLayout extends HandedLayout
So in the root layout, I have a custom LayoutParameters to accept a layout property, called handed. Here is the Layout Parameters class in
public static class LayoutParams extends ViewGroup.LayoutParams {
public int handed;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a =
c.obtainStyledAttributes(attrs, R.styleable.HandedLayout_LayoutParams);
handed = a.getInt(R.styleable.HandedLayout_LayoutParams_layout_handed,
HandedLayout.RIGHT_HANDED);
a.recycle();
}
public LayoutParams(int width, int height) {
super(width, height);
}
}
And as I understand it (shakily, it seems), I also need to override 3 classes in HandedLayout. Here they are:
#Override
public HandedLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new HandedLayout.LayoutParams(getContext(), attrs);
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
#Override
protected HandedLayout.LayoutParams generateDefaultLayoutParams() {
return new HandedLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
So in the inner custom layout (HandedPathLayout), I need lots of layout parameters, so I do the exact same thing! Here is the LayoutParameters class inside of HandedPathLayout:
public static class LayoutParams extends HandedLayout.LayoutParams {
public int[] endSides;
public int[] endPositions;
public int[] anchorSides;
public int[] anchorPositions;
public int controlPointDistance;
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
endSides = new int[2];
endPositions = new int[2];
anchorSides = new int[2];
anchorPositions = new int[2];
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HandedPathLayout_LayoutParams);;
try {
//this is the second place that the default handedness is set, watch out!
//handed = a.getInteger(R.styleable.HandedLayout_LayoutParams_layout_handed, HandedLayout.RIGHT_HANDED);
endSides[0] = a.getInteger(R.styleable.HandedPathLayout_LayoutParams_layout_from_side, HandedLayout.BOTTOM_SIDE);
endSides[1] = a.getInteger(R.styleable.HandedPathLayout_LayoutParams_layout_to_side, HandedLayout.PINKIE_SIDE);
endPositions[0] = a.getDimensionPixelSize(R.styleable.HandedPathLayout_LayoutParams_layout_from_position, 0);
endPositions[1] = a.getDimensionPixelSize(R.styleable.HandedPathLayout_LayoutParams_layout_to_position, 0);
anchorSides[0] = a.getInteger(R.styleable.HandedPathLayout_LayoutParams_layout_anchor_up_side, HandedLayout.TOP_SIDE);
anchorSides[1] = a.getInteger(R.styleable.HandedPathLayout_LayoutParams_layout_anchor_down_side, HandedLayout.BOTTOM_SIDE);
//todo: should these positions be set relative to handedness? which direction is zero?
anchorPositions[0] = a.getDimensionPixelSize(R.styleable.HandedPathLayout_LayoutParams_layout_anchor_up_position, 0);
anchorPositions[1] = a.getDimensionPixelSize(R.styleable.HandedPathLayout_LayoutParams_layout_anchor_down_position, 0);
controlPointDistance = a.getInteger(R.styleable.HandedPathLayout_LayoutParams_layout_control_point_position, HandedLayout.BOTTOM_SIDE);
} finally {
a.recycle();
}
}
}
And then again, in the HandedPathLayout class, the three functions to make sure we get the right kind of LayoutParams:
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof HandedPathLayout.LayoutParams;
}
#Override
public HandedPathLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new HandedPathLayout.LayoutParams(getContext(), attrs);
}
#Override
protected HandedPathLayout.LayoutParams generateDefaultLayoutParams() {
return new HandedPathLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
I've tried this with and without the fully qualified LayoutParams class names.
So, now that's all there and compiles okay, and then in onMeasure of the root ViewGroup, the HandedLayout, I loop through the children and attempt to get at their getLayoutParams() so I can figure out where to put them and how to measure them, and I get this ClassCastException!
05-29 18:16:35.061 9391-9391/com.codesolutions.onehandkeyboard E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.codesolutions.onehandkeyboard, PID: 9391
java.lang.ClassCastException: com.codesolutions.pathlayout.HandedLayout$LayoutParams cannot be cast to com.codesolutions.pathlayout.HandedPathLayout$LayoutParams
at com.codesolutions.pathlayout.HandedLayout.onMeasure(HandedLayout.java:90)
That exception is being thrown from this cast, which seems like it should get the right kind, in the debugger I've confirmed that the child is indeed a HandedPathLayout, and all indications are that the LayoutParams are of the correct type!
HandedPathLayout.LayoutParams lp =
(HandedPathLayout.LayoutParams)child.getLayoutParams();
I just don't get why the LayoutParams are not of the correct type!
And another thing about this I don't get is about the custom View (extends TextView) that I want to put into my HandedPathLayout. It needs a custom layout_rows XML attribute, so do I need to make it it's own LayoutParams? Those are only for ViewGroups, right? But then it's the view that needs that attribute applied, and not the ViewGroup.
UPDATE:
So when I run the debugger and stop just before my cast that fails, in the onMeasure of HandedLayout... I find that, indeed, the LayoutParams objects are NEITHER of the types I expect! The getLayoutParams of the parent (the HandedLayout) returns a FrameLayout.LayoutParams (which makes no sense to me at all) and the child (which is a HandedPathLayout) has a HandedLayout.LayoutParams! WHY?! What am I not understanding here?
Thank you!
Related
For a simplest example, with the (deprecated) AbsoluteLayout, you can freely drag the views around. But this following code wont let you:
public class TestLayout extends AbsoluteLayout {
public TestLayout(#NonNull Context context) {
super(context);
}
}
defining an additional inner class LayoutParams also wont help much
public static class LayoutParams extends AbsoluteLayout.LayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height, int x, int y) {
super(width, height,x, y);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
GIF:
cannot drag-edit a subclass of AbsoluteLayout
So how do I make a custom layout that's friendly to Android Studio's drag-editing mechanism?
Any help will be appreciated.
Step 1. Create a new app with Navigation Drawer template.
Step 2. Add a customized button and override the onMeasure method.
#CoordinatorLayout.DefaultBehavior(MyButton.Behavior.class)
public class MyButton extends android.support.v7.widget.AppCompatButton {
private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mPreDrawListener == null) {
mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
offsetTopAndBottom(50);
return true;
}
};
ViewParent p = getParent();
if (p instanceof View) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
((View)p).getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
}
}
}
}
public static class Behavior extends CoordinatorLayout.Behavior<MyButton> {
#Override
public boolean onLayoutChild(CoordinatorLayout parent, MyButton child, int layoutDirection) {
final List<View> dependencies = parent.getDependencies(child);
return super.onLayoutChild(parent, child, layoutDirection);
}
#Override
public void onAttachedToLayoutParams(#NonNull CoordinatorLayout.LayoutParams lp) {
if (lp.dodgeInsetEdges == Gravity.NO_GRAVITY) {
// If the developer hasn't set dodgeInsetEdges, lets set it to BOTTOM so that
// we dodge any Snackbars
lp.dodgeInsetEdges = Gravity.BOTTOM;
}
}
}
}
Step 3. Use MyButton in app_bar_main layout.
Step 4. Set a breakpoint in onPreDraw and then I could see it will be executed infinitely. If I comment the offsetTopAndBottom(50), everything goes fine.
I also trace the source code and find app receive vsync signal again and again which cause the onVsync function in Choreographer.java run infinitely. Why this happens?
Update
If I set a breakpoint as below and comment onPreDraw, this breakpoint finally will not be reached, otherwise, it can be reached always.
The callback onPreDraw() is called before each frame is drawn. Since you normally keep on drawing frames (~60fps), it is normal that it is called "infinitely".
To avoid this behavior, the usual pattern is to remove the listener as first statement in onPreDraw():
view.getViewTreeObserver().removeOnPreDrawListener(this);
where view is the downcasted parent in your case.
You can see example code in this video. The engineers are part of the Android Framework team.
I am trying to set a custom view to wrap_content in both height and width programatically. The problem is that no matter what i do it is always presented as match_parent.
This is the view
public class Circle extends View
{
public Circle(Context context) {
super(context);
}
public Circle(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public Circle(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(0xffff0000);
canvas.drawCircle(mmToPx(4), mmToPx(4), mmToPx(4), paint);
}
public float mmToPx(int mm) {
DisplayMetrics displayMetrics = this.getResources().getDisplayMetrics();
return mm * displayMetrics.xdpi * (1.0f/25.4f);
}
this is the main code
public void addCircle()
{
Circle circle = new Circle(this);
Random r = new Random();
FrameLayout.LayoutParams cParams = new FrameLayout.LayoutParams(-1, -2);
cParams.setMargins(r.nextInt((int) (w - (mmToPx(diam)))), r.nextInt((int) (h - (mmToPx(diam)))), 0, 0);
circle.setLayoutParams(cParams);
circle.setBackgroundColor(0xff00ff00);
Log.v("sd", circle.getLayoutParams().width + "");
circle.setId(R.id.circle);
circle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
count++; scoreTv.setText("Score:\n"+count);
frame.removeView(findViewById(R.id.circle));
addCircle();
}
});
frame.addView(circle);
}
Thank you for help :)
First- NEVER use magic numbers like -2. Use ViewGroup.LayoutParams.WRAP_CONTENT. Otherwise your code becomes difficult to read and may contain bugs (for example, if they changed the value).
Second- you aren't actually using cParams anywhere. You want to use the version of addView that takes the view and the layout params. Otherwise how will it know to use those layout params with that view.
Third- you don't have an onMeasure function in your view. Without that, how will it know how big your view is so it can actually wrap content on it? You'll need to define onMeasure so it can get the width and height of your view.
try this:
view.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))
I have a class MyLayout extending RelativeLayout which includes View type field. MyLayout object is created in xml layout file, so all properties are set there. I need to programatically set size of View field which depends on size of it's parent (MyLayout).
I was trying to set it in constructor, but when I try to use getWidth() method, it returns 0, so I assume that the size is not yet set inside a constructor. I was also trying to set it in onDraw() method, but when I run an application, this internal View is displayed for like second with it's default size and after that time it's scaled to the right size. Then I tried putting it inside onMeasure() method, but this one is called a few times, so again it doesn't seem to be efficient at all.
So what could be the best place to set it?
This is my class:
public class MyLayout extends RelativeLayout {
private View pointer;
public MyLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyLayout(Context context) {
super(context);
init(context);
}
private void init(Context c) {
pointer = new View(c);
pointer.setBackgroundResource(R.drawable.pointer);
addView(pointer);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)pointer.getLayoutParams();
lp.height = (int)(getHeight() * 0.198);
lp.width = (int)(getWidth() * 0.198);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
in your MyLayout class, override onSizeChanged():
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)pointer.getLayoutParams();
lp.height = (int)(getHeight() * 0.198);
lp.width = (int)(getWidth() * 0.198);
};
I'm fairly proficient at creating complex custom layouts based on ViewGroup. The only thing I'm missing is the ability to create my custom LayoutParams. I really need the ability to get the margins and why not create other extra params to pass in to the parent.
How can I go about creating a custom LayoutParam and using it via xml? I tried using a LinearLayout.LayoutParam but it's obviously crashing since the parent is not a LinearLayout. How can I work with LayoutParams on custom layouts?
Update:
As of now I'm sticking with using a FrameLayout and overriding the onMeasure and onLayout functions to do the layout myself. This does provide FrameLayout.LayoutParams. I'm guessing the childs would have to support the custom LayoutParam?
In your custom layout, create a nested class extending ViewGroup.LayoutParams. Then override some methods (all of the required ones are in my example). Here's a stripped-down version of one of my custom layouts:
public class MyLayout extends ViewGroup {
public MyLayout(Context context) {
}
public MyLayout(Context context, AttributeSet attrs) {
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
#Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
#Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return generateDefaultLayoutParams(); // TODO Change this?
}
public static class LayoutParams extends ViewGroup.LayoutParams {
public LayoutParams() {
}
public LayoutParams(int width, int height) {
}
public LayoutParams(Context context, AttributeSet attrs) {
}
}
}
Further explanation: How to create a FlowLayout (thanks for the link Luksprog!)