How to make a custom FrameLayout with a cover? - android

Looking to make a small Android Compound View/Custom Component, where a FrameLayout is extended so that it has its own fully overlaying "Cover" view (that can intercept clicks and ripple) without repeating that cover in every XML instance.
I don't know whether it is best practice to re-layout the covering View in the Custom FrameLayout's onMeasure or onLayout methods, or if there's a simple XML trick that I'm missing (outside of just using a RelativeView since that would draw and re-draw the layout very often, but if I'm wrong here too correct me please)
CoveredFrameLayout.class
public class CoveredFrameLayout extends FrameLayout {
private View coverView;
public CoveredFrameLayout(#NonNull Context context) {
this(context, null);
}
public CoveredFrameLayout(#NonNull Context context, #Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CoveredFrameLayout(#NonNull Context context, #Nullable AttributeSet attrs, #AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
inflate(context, R.layout.widget_covered_framelayout, this);
//
setBackgroundColor(ContextCompat.getColor(context, R.color.light_red));
coverView = findViewById(R.id.widget_cover);
//
coverView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
coverView.animate()
.alpha(0)
.setDuration(1000)
.withEndAction(new Runnable() {
#Override
public void run() {
coverView.setVisibility(GONE);
}
})
.start();
view.setOnClickListener(null);
}
});
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
}
widget_covered_framelayout.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<View
android:id="#+id/widget_cover"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="#color/transparent_yellow400"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:visibility="visible" />
</merge>
The XML CoveredFrameLayout being used
<com.example.oliver.content.ui.widgets.CoveredFrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="hello" />
</com.example.oliver.content.ui.widgets.CoveredFrameLayout>
And here's a screenshot of what this is looking like currently. Just trying to learn the best practice to get that yellow square to dynamically cover the whole FrameLayout! Thanks y'all!
Here is the end goal result, however this result was only achieved by hardcoding the Cover View's height and width to the pre-calculated height and width in pixels of the view itself. A dynamic version of this could work fine, but I don't know what is the best practice for where in the onMeasure/onLayout cycle to put this code.

Related

Custom LinearLayout - childs not visible

I build customView extending LinearLayout and having simple 3 childrens(2 TextView and ImageView). I create this view dynamically in code and adding it to parent LinearLayout. This view has background, so I can easily spot on the screen, that it is inflated correctly in its place, but any of child is not visible. I checked LayoutInspector and it shows that everything is setted correctly(text values to TextViews and picture to ImageView), but somehow when I try to locate them on inspector they are shown as little dot over my customView:
My CustomView is called DayTileView and this is square with gray background. As you can see on inspector on the left childrens are filled with content. Layout of View:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<merge>
<TextView
android:id="#+id/day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="#android:color/white"
/>
<TextView
android:id="#+id/dayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="#android:color/white"
/>
<ImageView
android:id="#+id/padlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="#drawable/ic_padlock"
/>
</merge>
</layout>
And its code:
public class DayTileView extends LinearLayout {
private DayTileBinding mBinding;
public DayTileView(Context context) {
super(context);
init();
}
public DayTileView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public DayTileView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mBinding = DataBindingUtil.inflate(LayoutInflater.from(getContext()), R.layout.day_tile, this, true);
setOrientation(VERTICAL);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
setMeasuredDimension(width, width);
}
public void setDay(int day, int month, int year) {
DateTime settedDay = new DateTime().withYear(year).withMonthOfYear(month).withDayOfMonth(day);
mBinding.day.setText(String.valueOf(day));
String dayName = settedDay.dayOfWeek().getAsText();
mBinding.dayName.setText(dayName);
boolean isWeekend = settedDay.dayOfWeek().get() == 6 || settedDay.dayOfWeek().get() == 7;
setBackgroundColor(ContextCompat.getColor(getContext(), isWeekend ? R.color.weekend_bg : R.color.weekday_bg));
}
}
Its use in another CustomView which is also LinearLayout but wiht horizontal orientation (PlannedDayView on inspector):
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<merge>
<*.customViews.DayTileView
android:id="#+id/dayTile"
android:layout_width="0dp"
android:layout_weight="0.2"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="#+id/container"
android:orientation="vertical"
android:layout_weight="1.2"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
</merge>
</layout>
Has anyone any idea what could be casuing this (childs out of view)? When I replace merge for LinearLayout with vertical orientation and same background everything in Design mode of layout is visible correctly, so it should work.
EDIT:
I found out, that if I set during View initalization Padding Top to 10px then dot is moving down. So it looks like from some reasons Android didn't made to inflate correctly TextViews and ImageView
I found out what was the problem:
I overrided onMeasure and didn't measure child Views. Earlier I was using such code to make square View not square ViewGroup.
Corrected code:
final int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
setMeasuredDimension(width, width);
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
After setting correct width and height for View I must measure whole view with new MeasureSpec

Rotated Textview not covering full page

I am using a TextView which has to rotate at angle at 35 degrees. I have successfully created a rotated textview but I can't fill the complete screen. Basically I want to complete 2 objectives-
1)Make the textview occupy the whole screen
currently it is showing it like this-
I wanna fill the space left in the top left corner. I covered the other three corners by the help of #Alexandre's answer. #Thanks Alexandre.
CustomTextView.java
public class CustomTextView extends android.support.v7.widget.AppCompatTextView {
public CustomTextView(Context context) {
super(context);
}
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec*2, heightMeasureSpec*3);
}
}
Layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:orientation="vertical">
<com.dakshansh.partytimevendorsapp.CustomTextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:rotation="-35"
<!--Hope I don't need to change text.Text is too much more.-->
android:text="Galleries" />
</LinearLayout>
2)Make the text justified
As you can see my text is left aligned & as I am trying to create a pattern, I would require the text to be justified
One solution would be to create a CustomTextView that extends TextView overriding [onMeasure](https://developer.android.com/reference/android/view/View.html#onMeasure(int, int)).
For example :
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec*2, heightMeasureSpec*2);
}
This would make your textView 4 times bigger filling the white spaces.
PS: I haven't tried this code.

how to put a custom view inside of a custom viewGroup / Layout

custom view inside of a custom viewGroup not visible, how can I get it to show up?
or is there a better way to do this that would work?
no compile or runtime errors but the view does not show up in the viewGroup, it is supposed to fill the area with color like the other views but it is white and the color for the view is not showing up inside of the CustomLayout
xml code, the first 2 views show up with no problems but the 3rd view that is nested inside of the CustomLayout does not show up, just white color area, the view inside not visible
CustomViewOne is a separate class file, CustomViewTwo and CustomViewThree are both nested inside the MainActivity class as static inner classes, and CustomLayout is a separate file
<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="vertical"
tools:context=".MainActivity" >
<com.example.customviewexample.CustomViewOne
android:layout_width="100dp"
android:layout_height="50dp" />
<view
class="com.example.customviewexample.MainActivity$CustomViewTwo"
android:layout_width="100dp"
android:layout_height="50dp" />
<com.example.customviewexample.CustomLayout
android:layout_width="100dp"
android:layout_height="50dp">
<view
class="com.example.customviewexample.MainActivity$CustomViewThree"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.example.customviewexample.CustomLayout>
</LinearLayout>
here is the code for the CustomViewThree, vary simple like the other custom Views it just fills the area with color, it is nested inside of the MainActivity so you have to use MainActivity$CustomViewThree to access it.
public static class CustomViewThree extends View {
public CustomViewThree(Context context) {
super(context);
}
public CustomViewThree(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomViewThree(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GREEN);
}
}
and here is the code for the CustomLayout class
public class CustomLayout extends FrameLayout {
public CustomLayout(Context context) {
super(context);
init(context);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CustomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public void init(Context context) {
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
custom view inside of a custom viewGroup not visible, how can I get it
to show up?
Your parent CustomLayout which wraps the child has a empty onLayout() method which makes the child to not appear. This method is important in a ViewGroup because it's used by the widget to place its children in it. So, you need to provide an implementation for this method to place the children(by calling the layout() method on each of them with the proper positions). As CustomLayout extends FrameLayout you could just call the super method to use FrameLayout's implementation or even better remove the overridden method(is there a reason for implementing it?).

ScrollView disable focus move

I have a simple input form; it's a vertical LinearLayout with EditTexts inside a ScrollView.
<ScrollView android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dip"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView style="#style/Text"
android:text="Name"/>
<EditText style="#style/EditBox"/>
</LinearLayout>
<View style="#style/Divider"/>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dip"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView style="#style/Text"
android:text="Password"/>
<EditText style="#style/EditBox"/>
</LinearLayout>
...
</LinearLayout>
</ScrollView>
When the user scrolls the form, it automatically moves its focus to the visible EditText.
It is possible to disable such behavior and always keep focus on the EditText currently selected by touch?
I understand that this may be a feature, but I need to disable it.
Thanks!
Just thought I'd share my solution to this. Even though some of the other answer's comments state that you cannot override this behavior, that is not true. This behavior stops as soon as you override the onRequestFocusInDescendants() method. So simply create your ScrollView extension to do this:
public class NonFocusingScrollView extends ScrollView {
public NonFocusingScrollView(Context context) {
super(context);
}
public NonFocusingScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonFocusingScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
return true;
}
}
And you're done. The ScrollView will mess with your focus no more.
I have had such a problem too. The only way that helped me is to extend scroll view and to override neigher
#Override
public ArrayList<View> getFocusables(int direction) {
return new ArrayList<View>();
}
or
#Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
return true;
}
but to override ViewGroup.requestChildFocus(View child, View focused) method as following:
#Override
public void requestChildFocus(View child, View focused) {
// avoid scrolling to focused view
// super.requestChildFocus(child, focused);
}
What worked for me was combining #dmon's and #waj's answers.
Only overriding onRequestFocusInDescendants() worked great when I was only dealing with EditTexts inside of the ScrollView, but when I started added multiple View types, it didn't work so well.
Only overriding getFocusables() did not work at all.
Overriding both onRequestFocusInDescendants() AND getFocusables() seems to work beautifully in all scenarios.
public class FixedFocusScrollView extends ScrollView {
public FixedFocusScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public FixedFocusScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FixedFocusScrollView(Context context) {
super(context);
}
#Override
public ArrayList<View> getFocusables(int direction) {
return new ArrayList<View>();
}
#Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
return true;
}
}
I tried all solutions posted here but either didn't work on certain Android versions or it messed up with some other behavior like when switching between touch and non-touch mode (when you click buttons or use the trackpad).
I finally found that overriding the method getFocusables did the trick:
public class FixedFocusScrollView extends ScrollView {
public FixedFocusScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public FixedFocusScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FixedFocusScrollView(Context context) {
super(context);
}
#Override
public ArrayList<View> getFocusables(int direction) {
return new ArrayList<View>();
}
}
Try to cut the problem from the source (edit: i.e. move it to an XML file).
First, there must be a focusable element for that to happen. Make all focusable elements contained in that scroll into non-focusable elements. Make them focusable after the view is inflated and is visible.
android:focusable="false"
android:focusableInTouchMode="false"
I found another easy solution that works for my problem.
I got a ScrollView with an EditText at the top, and after it, a big list of TextViews, RatingBars and Buttons. The Buttons launch AlertDialogs, and when they pops up, the ScrollView moves to the top, to the EditText that is the one that still has the focus.
To solve it, I set in the onClick method of the Activity the requestFocusFromTouch() to the view clicked (in this case the Button).
public void onClick(View v) {
v.requestFocusFromTouch();
...
}
So now when I click on a Button, the ScrollView moves and put that Button on the center of the screen, that was just what I wanted.
I hope it help.
This method is very effective,you can overload computeScrollDeltaToGetChildRectOnScreen and return 0
public class NoScrollFocusScrollView extends ScrollView {
public NoScrollFocusScrollView(Context context) {
super(context);
}
public NoScrollFocusScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoScrollFocusScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
return 0;
}
}
For such behavior use nextFocusUp and nextFocusDown.
<EditText
android:id="#+id/my_edit_text"
android:nextFocusDown="#id/my_edit_text"
android:nextFocusUp="#id/my_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</EditText>
Happened to me today, basically I was adding a View programmatically between some existing views, and Scroll automatically moved to focus that view. What I just did, is the following:
container.setFocusable( false );
This solved the issue for me. It may not be applicable in all situations, but works well for mine where the scrolling is done programmatically:
View originalFocus = getCurrentFocus();
scroller.fullScroll(View.FOCUS_DOWN);
if (originalFocus!=null) {originalFocus.requestFocusFromTouch();}

Android scrollview autoscrolling as text is added

I have a scrollview containing a textview in an Android app. This textview will have text appended to it continuously at set intervals. Scrolling works and the text adds just fine, but what I'd like to do is have the scrollview autoscroll down as text is added. As new text is appended at the bottom, it automatically scrolls down to match and old text is pushed out of sight at the top. What would be even better is to add text to the bottom of the scrollview and have it push older text upward, but one thing at a time.
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
>
<TextView
android:id="#+id/statusText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
</ScrollView>
I've tried the solution with gravity but result was a lot of empty space under actual text when scroll to bottom.
So that is another way. You can place your TextView inside of ScrollView:
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
And define addTextChangedListener
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
//some code here
ScrollView scrollView1 = (ScrollView) findViewById(R.id.scrollView1);
TextView textView1 = (TextView) findViewById(R.id.textView1);
textView1.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable arg0) {
scrollView1.fullScroll(ScrollView.FOCUS_DOWN);
// you can add a toast or whatever you want here
}
#Override
public void beforeTextChanged(CharSequence arg0, int arg1,
int arg2, int arg3) {
//override stub
}
#Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2,
int arg3) {
//override stub
}
}) }
Actually i noticed right now that the application will scroll every time the text is changed, not just added. However it works just fine for me.
You can simply scroll the whole ScrollView to the bottom when text is added.
textView.append(text);
scrollView.fullScroll(View.FOCUS_DOWN);
As #RaphaelRoyer-Rivard suggested in his comment, you can get more solid result with post:
textView.append(text);
scrollView.post(() -> scrollView.fullScroll(View.FOCUS_DOWN));
If you're looking for a more generic way of auto-scrolling to the bottom of a ScrollView, you could try the snippet below. It has the advantage that you don't need to post a Runnable to make sure you're in the UI thread. It has the disadvantage that I'm not sure what things this could break.
public class AutoScroller extends ScrollView {
public boolean autoscroll = true;
public AutoScroller(Context context) {super(context);}
public AutoScroller(Context context, AttributeSet attrs) {super(context,attrs);}
public AutoScroller(Context context, AttributeSet attrs, int defStyleAttr){super(context, attrs, defStyleAttr);}
public AutoScroller(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(autoscroll) {
scrollTo(getScrollX(), getChildAt(getChildCount()-1).getBottom() - getHeight());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Another candidat to override might be onLayout.
textView.append(text);
scroll.fullScroll(View.FOCUS_DOWN)
this will give problem, it won't scroll upto extreme top, due to android:layout_gravity="bottom"

Categories

Resources