Im trying to create a sort of HUD overlay for Google Cardboard.
The HUD needs to be duplicated (one for each eye). A simplistic solution would be to manually copy all the XML elements into another view but giving them different names. This feels like a bad approach since it involves lots of code duplication.
So i came up with the following solution for a ViewGroup with is supposed to render everything two times:
public class StereoView extends FrameLayout {
private static final String TAG = StereoView.class.getSimpleName();
public StereoView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
testPaint.setColor(Color.RED);
}
private Paint testPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right/2, bottom);
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.save();
canvas.translate(getWidth() / 2, 0);
super.dispatchDraw(canvas);
canvas.restore();
super.dispatchDraw(canvas);
}
public StereoView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public StereoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public StereoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
}
The first problem is that neither dispatchDraw or onDraw is called except from one or two times. It is not called when child views are invalidated.
The second problem is that background on elements which has a with of MATCH_PARENT renders outside the ViewGroups inner bounds:
200DP width
MATCH_PARENT
Is this approach hoping for too much, or am i thinking wrong? Creating a completely custom view to handle complex layouts and images seems like lots of work while copying my layout seems like bad design.
You say:
A simplistic solution would be to manually copy all the XML elements
into another view but giving them different names. This feels like a
bad approach since it involves lots of code duplication.
Actually you can go ahead and use the <include> tag. All you need to do is create a layout that contains all the views that you are going to show to a single eye. Then in your main layout you have to <include> this layout twice, one for the left eye and the other for the right eye.
You might wonder, if this is the case then how can i use findViewById() on this main layout, since now there will be two views with the same id. Well, you can fix that by doing it as follows. Let's say you have created the eye.xml layout. Then your main_layout should look like below.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<include
android:id="#+id/leftEye"
layout="#layout/eye" />
<include
android:id="#+id/rightEye"
layout="#layout/eye" />
</LinearLayout>
When you do the findViewById() in your code, you could do that as follows:
RelativeLayout leftEye = (RelativeLayout)findViewById(R.id.leftEye);
ImageView iv = (ImageView)leftEye.findViewById(R.id.something);
You need write a simple method in your activity where you just pass the leftEye or rightEye as a parameter and perform all code in this method. This lets you perform UI changes in leftEye and rightEye simultaneously.
In the future, you could write a custom View in which you could just inflate the eye.xml. That would modularize your idea.
This is my thoughts to your problem.
A ViewGroup hosts Views; any xml layout are Views, so extend a ViewGroup of your choice, either LinearLayout ,Framelayout-(i prefer), and in your initialisation process, inflate your Layout twice and add them as Views later you can research on how to use onLayout() to position your Views in your preferred Location.
And what ever you call a View 1, View 2 needs to be onboard, you can bind the two, using any approach you want, interfaces or beans
Note
you create one layout and inflate it twice. which will give you two separate View objects, hence this won't be code duplication as its more of
Elltz _20yearElltz = new Elltz(20),_21yearElltz = new Elltz(21);
Hope it helps
Related
So I'm working on creating a dialog fragment to allow user to choose from some options. I have a pretty simple layout inside a constraint layout. TextView on top, recycler view, then two buttons at the bottom.
The problem is, I want the recyclerview to be wrap content, so that if there aren't a lot of options, the dialog will shrink down. However, if there are a lot of options, i'd like it to expand but then start scrolling so all views are visible on the screen.
I can't seem to get past the situation where either it constantly is large. Or if I just allow wrap content, the dialog will grow so large the bottom buttons are missing.
I'm assuming it has something to do with some particular constraint options, but I can't figure out the combination. Any ideas?
EDIT: I know an easy answer is to set a max height on the recycler view. I'm hoping to do that same thing but with constraints, so its not a fixed hard height.
EDIT2: It looks like the constraints will work nicely with wrap as default if the view model's height is fixed. I really can't deal with a fixed height view model though...
Thanks
Create a customRecyclerView that override onMeasure method.
public class CustomRecyclerView extends RecyclerView{
public CustomRecyclerView (Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomRecyclerView (Context context) {
super(context);
}
public CustomRecyclerView (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
you can call the recyclerview like this
<com.example.yourpackage.CustomRecyclerView>
I've built a very simple native Android UI component and I want to update the size of its child view when a button from my react native project is clicked. To be more precise, when this button is clicked then I send a command to my SimpleViewManager which in turn calls the resizeLayout() of my custom view.
I can verify that the resizeLayout() is properly called but the layout is not resized until I rotate the phone. Obviously, changing the device's orientation triggers the draw() of my custom view but so does the invalidate() which I explicitly call.
Other layout changes like changing the background color instead of resizing it work fine.
My custom component looks like this:
public class CustomComponent extends RelativeLayout {
public CustomComponent(Context context) {
super(context);
init();
}
public CustomComponent(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomComponent(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
inflate(getContext(), R.layout.simple_layout, this);
}
public void resizeLayout(){
LinearLayout childLayout = (LinearLayout) findViewById(R.id.child_layout);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) childLayout.getLayoutParams();
layoutParams.height = 50;
layoutParams.width= 50;
childLayout.setLayoutParams(layoutParams);
invalidate();
}
}
and the simple_layout.xml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/child_layout"
android:layout_width="100dp"
android:layout_height="50dp"
android:background="#ffee11"/>
</RelativeLayout>
Any help would be greatly appreciated.
After a couple of days of searching, I finally found the answer in this old reported issue in React Native's repo.
This solution is the same as the ones used in ReactPicker.java and ReactToolbar.java. I had to put the following code in my CustomComponent and after that there is no need to even call requestLayout() or invalidate(). The changes are propagated as soon as I update my layout's LayoutParams.
#Override
public void requestLayout() {
super.requestLayout();
// The spinner relies on a measure + layout pass happening after it calls requestLayout().
// Without this, the widget never actually changes the selection and doesn't call the
// appropriate listeners. Since we override onLayout in our ViewGroups, a layout pass never
// happens after a call to requestLayout, so we simulate one here.
post(measureAndLayout);
}
private final Runnable measureAndLayout = new Runnable() {
#Override
public void run() {
measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
}
};
Try running
requestLayout();
instead of invalidate.
This post also has some good information on view lifecycle.
Usage of forceLayout(), requestLayout() and invalidate()
I would also recommend changing your root element in your view xml to
<merge>
that way your CustomComponent class doesn't have a RelativeLayout as its immediate child, unless that's what you were going for.
How do I set up a constructor in a custom TextView to be able to pass text from a fragment?
In other words, I'm confused how to send text from my fragment (Fragment1) to the custom view (View1):
public class View1 extends TextView {
//constructors:
public View1(Context context, AttributeSet ats, int ds) {
super(context, ats, ds);
init();
}
public View1(Context context) {
super(context);
init();
}
public View1(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
...
canvas.drawText(myString, margin1, margin2, paint); //myString is from Fragment1
....
}
I asked a similar question here, but didn't really get much help. Example code would go a long way towards clearing up my confusion. Thanks in advance!
You are extending a TextView anyway. As A--C mentioned, you can use getText(), as well as setText() to get and set the text.
In your context, I am not sure if it is a good idea to use TextView to implement your custom view/widget. View might be a better starting point, as TextView carries all kind of stuff around for formatting, icon/drawable display, click/button logic etc.
You need to define the standard constructors if you want to be able to have the system instantiate/inflate your components from an XML layout. Then you can use standard getters/setters for your data, same way as all other controls do it.
If you instantiate your widget/view yourself (in your code), you are free to define whatever constructors you want to (I believe).
I want to be able to mirror my app so it can be viewed in the windshield of a vehicle.
My XML has several nested LinearLayouts, TextViews and ImageViews. Currently I'm transforming each one and although it is mirrored, the structure of the elements is not (what was at the top is now at the bottom).
I've been looking for days and so far have tried a couple of approaches that have failed.
An animation that uses a matrix to flip on the X axis kind of works, except that it either reverts back or it stays and doesn't update, which is no good for interacting with the app.
I just tried to create a custom LinearLayout extending the parent one, hoping that I could apply a Matrix in its onDraw() method but that gives me a blank screen (I had to set setWillNotDraw(false); to hit the onDraw()).
Well eventually I found a solution that works well for me (so far it's caused no issues for users).
My solution was to override dispatchDraw to scale the canvas in my custom LinearLayout. Then I just needed to flip the touch events by overriding dispatchTouchEvent:
public class CustomContainer extends LinearLayout {
public CustomContainer(Context context) {
super(context);
this.setWillNotDraw(false);
}
public CustomContainer(Context context, AttributeSet attrs) {
super(context, attrs);
this.setWillNotDraw(false);
}
public CustomContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setWillNotDraw(false);
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
// Flip the view canvas
if (MyHUDActivity.mHUDMode) canvas.scale(1,-1, getWidth(), getHeight()/2f);
super.dispatchDraw(canvas);
canvas.restore();
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
// If in HUD mode then flip the touch zones
if (MyHUDActivity.mHUDMode) event.setLocation(event.getX(), getHeight()-event.getY());
return super.dispatchTouchEvent(event);
}
}
You can use the new Animation api to deal with reverting back after the horizontal flip.
I am trying to create a custom ViewGroup, and I want to use it with a full screen application. I am using the "requestWindowFeature(Window.FEATURE_NO_TITLE)" to hide the title bar. The title bar is not showing, but it still consuming space on top of the window.
The image above was generated with the following code:
public class CustomLayoutTestActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
Button b = new Button(this);
b.setText("Hello");
CustomLayout layout = new CustomLayout(this);
layout.addView(b);
setContentView(layout);
}
}
public class CustomLayout extends ViewGroup {
public CustomLayout(Context context) {
super(context);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i("CustomLayout", "changed="+changed+" l="+l+" t="+t+" r="+r+" b="+b);
final int childCount = getChildCount();
for (int i = 0; i < childCount; ++i) {
final View v = getChildAt(i);
v.layout(l, t, r, b);
}
}
}
(The full Eclipse project is here)
It is interesting to see that it is the Android that is given this space for my custom layout. I am setting the CustomLayout as the root layout of my Activity. In the Log in the "onLayout" is receiving "t=25", and that is what is pushing my layout down. What I don't know is what I am doing wrong that makes Android the "t=25" (which is exactly the height of the title bar).
I am running this code in the Android SDK 2.1, but I also happens in Android 2.2.
EDIT: If I change the CustomLayout class for some default layout (such as LinearLayout), the space disappears. Of course, the default layouts of Android SDK don't create the layout I am trying to create, so that is why I am creating one.
Although the layout I am creating is somewhat complex, this is the smallest code I could create reproducing the problem I have with my layout.
It's not a full answer, but in the meantime you can work around the problem by wrapping your custom layout in a <FrameLayout />
Also, it's worth noting that your layout extends beyond the bottom of the screen. It's shifted down by the title bar height (38 pixels in my emulator)
Edit: Got it. onLayout() (and the corresponding layout() method) specify that the coordinate are not relative to the screen origin, they're relative to the parent ( http://developer.android.com/reference/android/view/View.html#layout%28int,%20int,%20int,%20int%29 ). So the system is telling you that you're at relative coordinates (0, 38), and you're adding it when passing that down to your child, which means that you're saying that your child is at screen coordinates (0, 76), causing the gap.
What you actually want to do is:
v.layout(0, 0, r - l, b - t);
That will put your child Views aligned with the top left corner of your View, with the same width and height as your view.
I had the same issue with a FrameLayout in 2.2
I fixed it by adding android:layout_gravity="top" to the FrameLayout