How to calculate the height of a scrollView using ViewTreeObserver? - java

I have a ScrollView with some layouts in it, called the fragment_about_sl.xml.And the class associated to it is called AboutSLFragment.java.I want to calculate the height of the scrollView to get height ratio for an image.I researched the web and found this code.
ViewTreeObserver vto = scrollView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
scrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int scrollViewHeightInPixels = layout.getMeasuredHeight();
//This is equal with %45 weight you tried before
int height = (scrollViewHeightInPixels * 45) / 100;
ViewGroup.LayoutParams pagerParams = viewPager.getLayoutParams();
pagerParams.height = height;
}
});
As shown in the above code.It just have said scrollView instead of getting it from findViewById().And also in the below code,it just says layout.getMeasuredHeight() instead of specifying the name of layout.
int scrollViewHeightInPixels = layout.getMeasuredHeight();
The question is that I want to know what is meant by layout here(above).What should I put there,is it the id of the scrollView?If so I developed a code myself and I want to know whether it is correct.Please help me I am new to android.The id of the scrollview I used is aboutslscrollview.
final View view = rootView.findViewById(R.id.aboutslscrollview);
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int scrollViewHeightInPixels = view.getMeasuredHeight();
//This is equal with %45 weight you tried before
int height = (scrollViewHeightInPixels * 45) / 100;
ViewGroup.LayoutParams pagerParams = viewPager.getLayoutParams();
pagerParams.height = height;
}
});

Related

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 know the max amount of ItemViews that can be visible in the RecyclerView without loading the data?

I have a paging algorithm for the RecyclerView, which if done Scroll, a scrolling listener triggers and loads more elements. Initially, I assigned a number of elements to load in the request to the server by default that is 20 for my RecyclerView, which has a fixed size (not wrap_content).
I need to know the number of items that can be visible in the width/height of the defined RecyclerView before loading the data, to determine the amount of items to be requested in the initial load, given that with 20 items on some devices is not enough to activate the listener of the scroll and load more elements.
This is without considering the extra properties of the view, such as padding, margin etc ...
The solution can be in Java Android or Xamarin Android (not Forms) C#.
Update:
For you to have a clue, i have tried this and it works for me, only if I call it when the size of RecyclerView is assigned, inside the OnLayoutChange:
public int GetMaxVisibleItemCountFromRecyclerView(RecyclerView recyclerView)
{
if (recyclerView == null) return 0;
int Width = recyclerView.Width;
int Height = recyclerView.Height;
if (Width == 0 || Height == 0)
return 0;
var layoutManager = recyclerView.GetLayoutManager() as GridLayoutManager;
if (layoutManager == null) return 0;
int widthRatio = Width / layoutManager.SpanCount;
int quantity = (Height / widthRatio) * layoutManager.SpanCount;
return quantity;
}
This solution only works for RecyclerViews that use the GridLayoutManager. I have other RecyclerViews with defined sizes that also use the paging algorithm with a LinearLayoutManager.
I need a similar solution, that works with any LayoutManager of the RecyclerView and does not have to be called inside the OnLayoutChange, is this possible?
Instead of trying to replicate the computations that RecyclerView does to compute layout, let the system do the work for you. The following example lets RecyclerView lay out one item and measurements are taken from that. The dummy item is not displayed and is used just for measurement.
The advantage of this method is that we don't have to replicate what RecyclerView does to measure items. All key measurements are taken into account including padding, margins and decorations.
The following sample shows how this can be accomplished for GridLayoutManager and LinearLayoutManager. StaggeredGridLayoutManager and FlexboxLayoutManager are special cases and aren't taken into account here.
Here is a short video showing the results of this demo app showing that just one page of items was loaded.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private List<String> mItems = new ArrayList<>();
private RecyclerView mRecycler;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecycler = findViewById(R.id.recyclerView);
// Sample for vertical LinearLayoutManager.
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
// Sample for GridLayoutManager with 4 spans. Each item comsumes 2 spans.
// GridLayoutManager layoutManager = new GridLayoutManager(this, 4);
// layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
// #Override
// public int getSpanSize(int position) {
// return 2;
// }
// });
// Add single dummy item that will be measured but not be displayed.
mItems.add("Dummy item");
RecyclerViewAdapter mAdapter = new RecyclerViewAdapter(mItems);
mRecycler.setLayoutManager(layoutManager);
mRecycler.setAdapter(mAdapter);
// Take measurements in OnPreDraawListener(). This could also be accomplished with
// mRecyclerView.post(new Runnable()...)
mRecycler.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
mRecycler.getViewTreeObserver().removeOnPreDrawListener(this);
// RecyclerView is laid out with single dummy entry. Get how many of this type
// of item can fit into the visible portion of the RecyclerView.
final int nItems = getInitialLoadCount(mRecycler);
Log.d(TAG, "<<<<Items per page=" + nItems);
// Don't need the dummy entry any more.
mItems.clear();
mRecycler.getAdapter().notifyDataSetChanged();
mItems = new ArrayList<>();
// Fake load...
loadInitialItems(nItems);
return false;
}
});
}
// Determine how many items will fill one screen of the RecyclerView. Call with the
// RecyclerView loaded with at least one item for measurement.
private int getInitialLoadCount(RecyclerView recyclerView) {
int itemsToLoad = 0;
RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
View firstChild = recyclerView.getChildAt(0);
if (lm instanceof LinearLayoutManager) {
Rect bounds = new Rect();
recyclerView.getDecoratedBoundsWithMargins(firstChild, bounds);
if (lm.canScrollVertically()) {
int recyclerHeightForItems = recyclerView.getHeight() - recyclerView.getPaddingTop()
- recyclerView.getPaddingBottom();
itemsToLoad = (recyclerHeightForItems + bounds.height() - 1) / bounds.height();
} else if (lm.canScrollHorizontally()) {
int recyclerWidthForItems = recyclerView.getWidth() - recyclerView.getPaddingLeft()
- recyclerView.getPaddingRight();
itemsToLoad = (recyclerWidthForItems + bounds.width() - 1) / bounds.width();
}
if (lm instanceof GridLayoutManager) {
// Adjust for GridLayoutManager. All items should to be the same number of spans.
GridLayoutManager glm = (GridLayoutManager) lm;
itemsToLoad *= glm.getSpanCount() / glm.getSpanSizeLookup().getSpanSize(0);
}
}
return itemsToLoad;
}
private void loadInitialItems(final int itemCount) {
// Simulate load of nItems...should be on non-UI thread.
new Thread(new Runnable() {
#Override
public void run() {
for (int i = 1; i <= itemCount; i++) {
sleep(250);
mItems.add("Item #" + i);
}
runOnUiThread(new Runnable() {
#Override
public void run() {
mRecycler.swapAdapter(new RecyclerViewAdapter(mItems), true);
}
});
}
}).start();
}
private static final String TAG = "MainActivity";
}
If your RecyclerView and your item width and height are defined in dp, then you should save their dimensions in dimen.xml. Then you can calculate how many items will fit like this:
float recyclerHeight = getResources().getDimension(R.dimen.recycler_height);
float itemHeight = getResources().getDimension(R.dimen.item_height);
int numOfItemsFit = (int) (recyclerHeight / itemHeight);
If your recyclerView isn't defined by dp, but the rest of the views in this layout are you can try to accomplish the same thing by decreasing the other views height from the total view height. You can check the total view height with:
public int getHeight() {
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size.y;
}
If you can't do the above you'll need to measure your RecyclerView inside of OnLayoutChange :( . You can do that with: recyclerView.getMeasuredHeight(); and recyclerView.getMeasuredWidth();

How do I Make an ImageView twice the size of screen Resolution

I am trying to set the height and width of an imageview to be twice the size of the height and width of the screen, which obviously changes depending on what device is used.
This is what I attempted (but the imageview was just invisible):
public class MainActivity extends AppCompatActivity {
private ImageView Geoline;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Geoline = findViewById(R.id.Geoline);
//Fetching the Device Resolution
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int scrWidth = size.x;
int scrHeight = size.y;
//Creating variables that are twice the size of screen resolutions
int scalex = 2 * scrWidth;
int scaley = 2 * scrHeight;
//Setting the screen width and height to equal that of variables "scalex" and "scaley"
Geoline.setScaleX(scalex);
Geoline.setScaleY(scaley);
}
You are trying to do this in onCreate() where View is not yet rendered, trying moving your code to onResume() or even better do like this
mBinding.parentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
}
});
Move your code inside this, it will work.

My ImageButton isn't rescaling like the code is telling it to. Infact it's not doing anything

My ImageButton isn't rescaling like the code is telling it to. Infact it's not doing anything.
Code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayShowCustomEnabled(true);
setTitle("Valour");
setContentView(R.layout.activity_main_screen);
getScreenRes();
}
public void getScreenRes() {
DisplayMetrics display = this.getResources().getDisplayMetrics();
int width = display.widthPixels;
int height = display.heightPixels;
int buttonheight = display.heightPixels / 8;
double buttonwidth = buttonheight * 2.66666667;
int buttonwidthint = (int) Math.round(buttonwidth);
ImageButton firsttimeFB = (ImageButton) findViewById(R.id.firsttime_fb);
firsttimeFB.getLayoutParams().width = buttonwidthint;
firsttimeFB.getLayoutParams().height = buttonheight;
}
XML:
<ImageButton
android:layout_width="50dip"
android:layout_height="50dip"
android:background="#drawable/facebook"
android:id="#+id/firsttime_fb"
/>
And it ends up looking like this:
call setLayoutParams() from the firsttimeFB after change the width and height.
Try using android:src and change the ScaleType, see if that makes it scale better.
Also after setting the width and hight by calling getLayoutParams() you should call request layout or the changes will not be displayed.
If you want to set the width based on the height or vice versa you should create your own custom ImageButton class which contains the following code:
public class CustomImageButton extends ImageButton{
...
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = getMeasuredHeight();
float constant = 2.6666;
setMeasuredDimension(height * constant, height);
}
...
}
What I want to add is that I am wondering what you are trying to do. I can not think of a senario in which I need the screensize to determen the size of the button. Try to rethink your approach and ask yourself if what you are doing is making any sense.
Try this:
public void getScreenRes(){
DisplayMetrics display = this.getResources().getDisplayMetrics();
int width = display.widthPixels;
int height = display.heightPixels;
int buttonheight = display.heightPixels / 8;
double buttonwidth = buttonheight * 2.66666667;
int buttonwidthint = (int) Math.round(buttonwidth);
ImageButton firsttimeFB = (ImageButton) findViewById(R.id.firsttime_fb);
LayoutParams lp = firsttimeFB.getLayoutParams();
lp.width = buttonwidthint;
lp.height = buttonheight;
firsttimeFB.setLayoutParams(lp);
}

Android Issue with activity context

So I have a helper class that reuses a lot of code through out the application, one of the methods is shown below:
public void setTitleTextSize(final int id){
infoButton = ((Activity) context).findViewById(R.id.info_button);
ViewTreeObserver customTitleScale = infoButton.getViewTreeObserver();
customTitleScale.addOnPreDrawListener(new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
int infoWidth = infoButton.getMeasuredWidth();
if(infoWidth !=0){
if(Build.VERSION.SDK_INT >= 11.0){
TypedValue tv = new TypedValue();
((Activity) context).getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
actionBarWidth = ((Activity) context).getResources().getDimensionPixelSize(tv.resourceId);
}
DisplayMetrics metrics = new DisplayMetrics();
Display Screen = ((Activity) context).getWindowManager().getDefaultDisplay();
Screen.getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int titleWidth = screenWidth - infoWidth - actionBarWidth;
TextView titleText = (TextView) ((Activity) context).findViewById(R.id.title_text);
titleText.setText(id);
TextPaint paint = titleText.getPaint();
Rect rect = new Rect();
String text = String.valueOf(titleText.getText());
int textLength = text.length();
paint.getTextBounds(text, 0, textLength, rect);
if(rect.width() > titleWidth){
float scale = (float) titleWidth / (float) rect.width();
float textSize = titleText.getTextSize();
float scaleSize = (float) (textSize * (scale*0.8));
titleText.setTextSize(TypedValue.COMPLEX_UNIT_PX, scaleSize);
}
infoButton.getViewTreeObserver().removeOnPreDrawListener(this);
}
return false;
}
});
}
I use this particular method on all of my activities.
The problem I've got is I don't want to display the infoButton on every activity but when I add View infoButton = findViewById(R.id.info_button); infoButton.setVisibility(View.GONE); to the activity, the screen is just black.
So I was thinking on how to do this and the only thing I can thing of is to pass a boolean into the method stating whether the info view is visible or not. I suppose I'd do an if statement saying `if true then display it, if false then don't but I can't figure out how to do this.
Any help would be amazing thanks.
Try to use method infoButton.getVisibility()
it will return you the integer to get visibility status of you widget.
I figured out the issue I was having, My logic wasn't correct for the if statement if(infoWidth !=0). The code wasn't running if the activity didn't have an info button because the infoWidth would always be zero. So I changed the logic to if(infoWidth !=0 || !displayInfoButton) which works perfectly.

Categories