I am trying to make a custom view that is square, using the width as the height. I am also using a pre-defined layout which I inflate as it's UI. As soon as I overrode onMeasure, the custom view no longer appears. Here is my code:
public class MyView extends RelativeLayout{
public MyView(Context context) {
super(context);
addView(setupLayout(context));
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
addView(setupLayout(context));
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
addView(setupLayout(context));
}
private View setupLayout(Context context) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View myView = inflater.inflate(R.layout.view_layout, null);
return myView;
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(widthMeasureSpec));
}
}
I have 2 questions:
How do I override onMeasure so that it draws my view the way I am expecting it to?
Is there any way I can make this more efficient in terms of the view hierarchy (i.e. not be putting a RelativeLayout inside a RelativeLayout)
You can use this code from Jan Němec's answer to a similar question :
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
public class SquareLayout extends LinearLayout {
public SquareLayout(Context context) {
super(context);
}
public SquareLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (width > (int)(mScale * height + 0.5)) {
width = (int)(mScale * height + 0.5);
} else {
height = (int)(width / mScale + 0.5);
}
super.onMeasure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
);
}
}
Or try to use this library project.
Related
RecyclerView inside NestedScrollView freezes the Activity when loading large amount of data. It loads much faster with ScrollView, but scrolling is affected in that case.
I tried setting attributes like setAutoMeasure and setNestedScrollingEnabled which did not help.
Any suggestions?
Recycling of views is not supported inside NestedScrollViews as I understand, so the suggestion would be to try to change your layout.
This help to increase the speed of recycler view scrolling.
SpeedyLinearLayoutManager linearLayoutManager = new SpeedyLinearLayoutManager(MainActivity.this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
myRecyclerView.setLayoutManager(linearLayoutManager);
SpeedyLinearLayoutManager.class
public class SpeedyLinearLayoutManager extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 2f; //default is 25f (bigger = slower)
public SpeedyLinearLayoutManager(Context context) {
super(context);
}
public SpeedyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public SpeedyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return SpeedyLinearLayoutManager.this.computeScrollVectorForPosition(targetPosition);
}
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
}
Use Custom list view instead of Recycler view inside scroll view...
Check this Answer
Custom List View
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ListView;
public class ExpandableHeightListView extends ListView {
boolean expanded = true;
public ExpandableHeightListView(Context context) {
super(context);
}
public ExpandableHeightListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExpandableHeightListView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public boolean isExpanded() {
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// HACK! TAKE THAT ANDROID!
if (isExpanded()) {
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
}
you can include app:layout_behavior="#string/appbar_scrolling_view_behavior"
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
or disable the nested scrolling behavior for the recycler view.
recyclerView.setNestedScrollingEnabled(false);
I am trying to create a textview that will always be squared so I have implemented this custom class.
public class SquareTextView extends TextView {
public SquareTextView(Context context) {
super(context);
}
public SquareTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int max = Math.max(getMeasuredWidth(), getMeasuredHeight());
setMeasuredDimension(max, max);
}
}
Here is an example layout that illustrates the problem:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp">
<com.mypackage.SquareTextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="right|top"
android:background="#000"
android:gravity="center"
android:padding="4dp"
android:text="1"
android:textColor="#FFF"/>
</LinearLayout>
Here is an image of this
This works great in getting the view squared, however, it seems like the gravity gets messed up. With this, the text seems to always be in the top left corner. How can I have a TextView that will always be squared but still keep the gravity or at least be able to center the text?
EDIT: After some testing I have noticed that if you set the width or height to a specific dp size the gravity seems to be working again. So it probably has to do with the WRAP_CONTENT attribute. Will that be handled in another way in the onmeasure method that could cause my own method to not work as expected?
Hope you have already got the answer by now. If not, you can use this:
public class TextAlphaSquareTextView extends AppCompatTextView {
private int mTextAlpha = 0;
private boolean isSquare = false;
public TextAlphaSquareTextView(Context context) {
super(context);
init(null);
}
public TextAlphaSquareTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public TextAlphaSquareTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
if (attrs == null) {
} else {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.TextAlphaSquareTextView);
mTextAlpha = a.getInteger(R.styleable.TextAlphaSquareTextView_textAlpha, 100);
isSquare = a.getBoolean(R.styleable.TextAlphaSquareTextView_squareMode, false);
a.recycle();
if(mTextAlpha < 0 || mTextAlpha > 100)
throw new IllegalArgumentException("Alpha range should be b/w 0 to 100 (in percentage)");
else {
setAlphaOnTextColor();
}
}
setText(getText());
}
void setAlphaOnTextColor() {
int alpha = ((255 * mTextAlpha) / 100);
setTextColor(getTextColors().withAlpha(alpha));
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (isSquare) {
int width = this.getMeasuredWidth();
int height = this.getMeasuredHeight();
int size = Math.max(width, height);
int widthSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(widthSpec, heightSpec);
}
}
}
you need to call super.onMeasure() again with EXACT spec and the calculated size, since setMeasureDimension() seems to be ignoring the gravity.
Before RecyclerView, I used this class to make grid's height dependant on it's childs(so it works fine in scrollview):
public class FullHeightGridView extends GridView
{
public FullHeightGridView(Context context)
{
super(context);
}
public FullHeightGridView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public FullHeightGridView(Context context, AttributeSet attrs,
int defStyle)
{
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
How can I modify GridLayoutManager to behave in the same way?
I am creating a custom imageview and I am trying to find the height of the parent. The only detail I know about the parent is that it would potentially scroll. The reason I need the parent's height is because I am trying to figure out the imageview's position on the screen. I have made an equation that works for accurately calculating its position, but it only works when I manually enter in the parents height. Is there any way to retrieve this information or is there another way to get my imageview's position on the screen every time it changes?
Try this way
public class MyImageView extends ImageView {
int parentHeight;
int parentWidth;
public MyImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyImageView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(((View)this.getParent()).getMeasuredWidth()!=0){
parentHeight = ((View)this.getParent()).getMeasuredHeight();
parentWidth = ((View)this.getParent()).getMeasuredWidth();
}
}
}
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);
};