I want to implement a custom LayoutInflater that scales dimension values, for example,
use:
int scale = 2;
MyInflater.inflate(R.id.testview, null, parent, scale);
will inflate a xml with all dimension values doubled.
that is, a xml like this:
<View android:layout_width="10dp" android:layout_height="10dp" />
will inflate to a view that width and height are 20dp.
LayoutInflater.Factory cannot solve my problem.
Is there a way that I can achieve this?
Perhaps you could use this code to loop all the children of your inflated layout and multiply the width and height by setting the LayoutParams:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
MyInflater inflater = new MyInflater(LayoutInflater.from(this), this);
ViewGroup viewGroup = (ViewGroup) findViewById(R.id.my_layout);
inflater.inflate(R.layout.test_view, viewGroup, true, 2);
}
private class MyInflater extends LayoutInflater {
private int mScale;
protected MyInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
#Override
public LayoutInflater cloneInContext(Context newContext) {
return null;
}
public View inflate(int resource, ViewGroup root, boolean attachToRoot, int scale) {
mScale = scale;
// This will return the parent of the inflated resource (if it has one)
View viewRoot = super.inflate(resource, root, attachToRoot);
if (viewRoot instanceof ViewGroup) {
loopViewGroup((ViewGroup) viewRoot, viewRoot == root);
} else {
doubleDimensions(viewRoot);
}
return viewRoot;
}
private void doubleDimensions(View view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
Log.d(TAG, "Before => "+params.width+" : "+params.height);
params.width = params.width * mScale;
params.height = params.height * mScale;
Log.d(TAG, "After => "+params.width+" : "+params.height);
view.setLayoutParams(params);
}
private void loopViewGroup(ViewGroup group, boolean isRoot) {
// If viewRoot == root, skip setting ViewGroup params
if (!isRoot) doubleDimensions(group);
// Loop the ViewGroup children
for (int i=0; i<group.getChildCount(); i++) {
View child = group.getChildAt(i);
// If a child is another ViewGroup, loop it too
if (child instanceof ViewGroup) {
loopViewGroup((ViewGroup) child, false);
} else {
doubleDimensions(child);
}
}
}
}
Related
I'm trying to build a dynamic help overlay screen for my app. I have the basics working fine for the elements that are already visible or invisible, but when elements are initially Visibility.GONE (which I'm changing to visible before building the view) they still are found with position 0,0 and size 0x0.
I've tried adding short wait() loops, onLayoutChangedListener(), and getViewTreeObserver().addOnGlobalLayoutListener() which did nothing.
in onOptionsSelected():
HelpUtils helpUtils = new HelpUtils(mView, requireContext());
final View helpView = helpUtils.getHelpView();
final View mainLayout = helpUtils.getMainLayout();
View shiftFragment = mainLayout.findViewById(R.id.current_shift_layout);
Map<View, Integer> visibilityMap = new HashMap<>();
for (int i = 0; i < ((ViewGroup) shiftFragment).getChildCount(); i++) {
View child = ((ViewGroup)shiftFragment).getChildAt(i);
visibilityMap.put(child, child.getVisibility());
child.setVisibility(View.VISIBLE);
}
final ViewGroup viewGroup = helpUtils.getHelpViewGroup(targets);
if (firstTime) {
((FrameLayout)helpView).addView(viewGroup);
}
helpView.setVisibility(View.VISIBLE);
HelpUtils class:
public class HelpUtils {
final private View mView;
final private Context mContext;
public HelpUtils(View view, Context context) {
mView = view;
mContext = context;
}
public View getMainLayout() {
return mView.getRootView().findViewById(R.id.main_layout);
}
public View getHelpView() {
return getMainLayout().findViewById(R.id.help_view);
}
private void recursiveWalkViews(ViewGroup viewGroup, SparseArray<View> result, Set<Integer> targets) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View v = viewGroup.getChildAt(i);
if(v instanceof ViewGroup) {
recursiveWalkViews(((ViewGroup) v), result, targets);
} else {
if (targets.contains(v.getId())) {
result.put(v.getId(), v);
}
}
}
}
public SparseArray<View> getViewMap(Set<Integer> targets) {
SparseArray<View> map = new SparseArray<>();
ViewGroup view = (ViewGroup) mView;
recursiveWalkViews(view, map, targets);
return map;
}
private int getActionBarHeight() {
int[] textSizeAttr = new int[]{R.attr.actionBarSize};
TypedArray a = mContext.obtainStyledAttributes(new TypedValue().data, textSizeAttr);
int height = a.getDimensionPixelSize(0, 0);
a.recycle();
return height;
}
/**
* Match the layout of a new {#link TextView} to the layout of an existing view.
* #param source The {#link View} to be matched to.
* #param txt The text for the new {#link TextView}
* #return A new {#link TextView}
*/
public TextView matchParams(View source, String txt) {
int x, y;
float sp, size;
int[] location = new int[2];
int offset;
source.getLocationInWindow(location);
x = location[0];
y = location[1];
offset = source.getPaddingLeft();
size = ((TextView) source).getTextSize();
sp = size/mContext.getResources().getDisplayMetrics().scaledDensity;
TextView textView = new TextView(mContext);
textView.setWidth(source.getWidth() - source.getPaddingRight());
textView.setTextSize(14);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if(source instanceof MaterialButton) {
params.topMargin = y + offset + ((MaterialButton)source).getPaddingBottom() - getActionBarHeight();
} else {
params.topMargin = y + offset - getActionBarHeight();
}
params.leftMargin = x + 8;
textView.setLayoutParams(params);
textView.setBackgroundColor(mContext.getResources().getColor(android.R.color.white));
textView.setText(txt);
return textView;
}
public ViewGroup getHelpViewGroup(Map<Integer, String> helpViews) {
FrameLayout layout = new FrameLayout(mContext);
SparseArray<View> views = getViewMap(helpViews.keySet());
List<TextView> textViews = new ArrayList<>(views.size());
for (int i = 0; i < views.size(); i++) {
View view = views.valueAt(i);
textViews.add(matchParams(views.valueAt(i), helpViews.get(views.keyAt(i))));
}
for (TextView textView : textViews) {
layout.addView(textView);
}
return layout;
}
}
Layout Inspector shows the overlay views are drawn at 0,0 with a width of 0, but the views that were changed to VISIBLE are in the correct place.
In onOptionsSelected() wrap your new view creation in a Runnable() from the target view (I'm guessing that's shiftFragment since that's where you're setting all the views to View.VISIBLE.
Basically make the following changes.
before:
for (int i = 0; i < ((ViewGroup) shiftFragment).getChildCount(); i++) {
View child = ((ViewGroup)shiftFragment).getChildAt(i);
visibilityMap.put(child, child.getVisibility());
child.setVisibility(View.VISIBLE);
}
final ViewGroup viewGroup = helpUtils.getHelpViewGroup(targets);
if (firstTime) {
((FrameLayout)helpView).addView(viewGroup);
}
helpView.setVisibility(View.VISIBLE);
after:
for (int i = 0; i < ((ViewGroup) shiftFragment).getChildCount(); i++) {
View child = ((ViewGroup)shiftFragment).getChildAt(i);
visibilityMap.put(child, child.getVisibility());
child.setVisibility(View.VISIBLE);
}
shiftFragment.post(new Runnable() {
#Override
public void run() {
final ViewGroup viewGroup = helpUtils.getHelpViewGroup(targets);
if (firstTime) {
((FrameLayout)helpView).addView(viewGroup);
}
helpView.setVisibility(View.VISIBLE);
);
The snippet below does the following:
Gets the parent view and posts a Runnable on the UI thread. This
ensures that the parent lays out its children before calling the
getHitRect() method. The getHitRect() method gets the child's hit
rectangle (touchable area) in the parent's coordinates.
from Manage touch events in a ViewGroup
I want to change the height of the view and decrease height all the bottom view. This works well in the fragment, but does not work in the adapter.
I do not want to squeeze the main view, I need to increase only the size of containerAvatar.
public class OrderAdapter extends ArrayAdapter {
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = layoutInflater.inflate(R.layout.open_order_item, parent, false);
containerAvatar = convertView.findViewById(R.id.container_avatar_size);
imgAvatar = convertView.findViewById(R.id.img_open_order_avatar);
Glide.with(context)
.load("http://neuronovosti.ru/wp-content/uploads/2017/12/Drosophila-Brain-1024x1024.jpg")
.into(imgAvatar);
containerAvatar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
final ViewGroup.LayoutParams lp = containerAvatar.getLayoutParams();
final ResizeAnimation a = new ResizeAnimation(containerAvatar);
a.setDuration(500);
a.setParams(lp.height, lp.height * 2);
containerAvatar.startAnimation(a);
}
});
return convertView;
}
public class ResizeAnimation extends Animation {
private int startHeight;
private int deltaHeight; // distance between start and end height
private View view;
public ResizeAnimation(View v) {
this.view = v;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = (int) (startHeight + deltaHeight * interpolatedTime);
view.requestLayout();
}
public void setParams(int start, int end) {
this.startHeight = start;
deltaHeight = end - startHeight;
}
#Override
public void setDuration(long durationMillis) {
super.setDuration(durationMillis);
}
#Override
public boolean willChangeBounds() {
return true;
}
That's what you need
Try with setLayoutParams() Method of View -
private void setDimensions(View view, int width, int height){
android.view.ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = width;
params.height = height;
view.setLayoutParams(params);
}
Added it on your Adapter -
public class OrderAdapter extends ArrayAdapter {
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = layoutInflater.inflate(R.layout.open_order_item, parent, false);
containerAvatar = convertView.findViewById(R.id.container_avatar_size);
imgAvatar = convertView.findViewById(R.id.img_open_order_avatar);
Glide.with(context)
.load("http://neuronovosti.ru/wp-content/uploads/2017/12/Drosophila-Brain-1024x1024.jpg")
.into(imgAvatar);
containerAvatar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
setDimensions(containerAvatar, 100, 100);
}
});
return convertView;
}
private void setDimensions(View view, int width, int height){
android.view.ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = width;
params.height = height;
view.setLayoutParams(params);
}
}
I have the following custom relative layout:
public class CircleView extends RelativeLayout implements OnDragListener {
private DropCallback onDrop = null;
private ImageButton imageButton = null;
private int radius = -1;
private double step = -1;
private double angle = -1;
private static final int CENTER_ID = 111;
public CircleView(Context context, DropCallback onDrop, int radius, List<View> views) {
super(context);
this.onDrop = onDrop;
this.radius = radius;
this.step = (2 * Math.PI) / views.size();
this.initView(context, views);
}
private void initView(Context context, List<View> views) {
RelativeLayout.LayoutParams layoutParamsView = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
this.setLayoutParams(layoutParamsView);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
this.imageButton = new ImageButton(context);
this.imageButton.setId(CENTER_ID);
this.imageButton.setLayoutParams(layoutParams);
this.imageButton.setImageResource(R.drawable.ic_power_on);
this.imageButton.getBackground().setAlpha(0);
this.imageButton.setOnDragListener(this);
this.addView(this.imageButton);
for(View view : views) {
this.addView(this.placeView(view));
}
}
private View placeView(View view) {
view.measure(0, 0);
this.imageButton.measure(0, 0);
int x = (int)((view.getMeasuredWidth() / 2) + this.radius * Math.cos(this.angle));
int y = (int)((view.getMeasuredHeight() / 2) + this.radius * Math.sin(this.angle));
this.angle += this.step;
int deltaX = view.getMeasuredWidth();
int deltaY = view.getMeasuredHeight();
int deltaImageX = this.imageButton.getMeasuredWidth() / 2;
int deltaImageY = this.imageButton.getMeasuredHeight() / 2;
int xToDraw = ((x - deltaX) - deltaImageX);
int yToDraw = ((y - deltaY) - deltaImageY);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ABOVE, CENTER_ID);
layoutParams.addRule(RelativeLayout.RIGHT_OF, CENTER_ID);
layoutParams.setMargins(xToDraw, 0, 0, yToDraw);
view.setLayoutParams(layoutParams);
return view;
}
#Override
public boolean onDrag(View view, DragEvent event) {
return this.onDrop.onDrop(view, event);
}
}
I have no xml layout file for this and it worked until now.
Today i changed the activity using this custom layout to be a Fragment and i now need to set the layout. Unfortunately something like the following isnt possible:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
return inflater.inflate(new CircleView(this, this, 240, this.views), container, false);
}
So how can i inflate my custom relative layout?
Just create an instance of Circle view and add it to container :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
CircleView circleView = new CircleView(this, this, 240, this.views);
...
return circleView;
}
You should create you xml layout, for example circle_view.xml:
<your_package.CircleView android:id="#+id/gcircle_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
and then:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
return inflater.inflate(R.layout.circle_view, container, false);
}
btw, you should implement constructors:
public CircleView(Context context){
super(context);
}
public CircleView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public CircleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
You can create the inflator like this:
LayoutInflater inflater = LayoutInflater.from(context);
Then use the inflator to inflate the needed.
I have a proper list view which has an adapter that extends BaseAdapter.
Everything works fine, but I need a margin of 30 dp for index 0. How to achieve this?
I tried the below 2 code but it crashes.
View subView = createButtonView((MenuButton) getItem(position));
if(position==0){
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)subView.getLayoutParams();
layoutParams.setMargins(30, 0, 0, 0);
subView.setLayoutParams(layoutParams);
}
return subView;
public View getSubView(int position){
View subView = createButtonView((MenuButton) getItem(position));
if(position==0){
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));
subView.setLayoutParams(layoutParams);
}
return subView;
}
Also I cannot use a headerView because the child view might not always be a same view, its will be dynamic.
simple alternative is use
SpaceView with 30dp width in xml( space view is very light view in android)
and adjust the visiblity for positions
if (position == 0)
yourxmlSpaceView.setVisibility(View.VISIBLE);
else
yourxmlSpaceView.setVisibility(View.GONE);
for 30dp you can use this method to convert int to pixels
public int dpToPx(int dp) {
DisplayMetrics displayMetrics = context.getResources()
.getDisplayMetrics();
int px = Math.round(dp
* (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
return px;
}
In the adapter class check for position zero and specify your layout parameters inside that if clause and add it to the convertView
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
LayoutInflater inflater = (LayoutInflater) mcontext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.adapter_main, convertView, false);
}
if(position == 0){
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)subView.getLayoutParams();
layoutParams.setMargins(30, 0, 0, 0);
convertView.setLayoutParams(layoutParams);
}
return view;
}
your listview can add a view to fake space with height is 30dp.
View:
<View
android:layout_width="match_parent"
android:layout_height="30dp"
android:id="#+id/fake_view"
/>
adapter:
public View getView(int position, View v, ViewGroup parent){
....
View fakeView = v.findViewById(R.id.fake_view);
fakeView.setVisibility(position == 1? View.VISIBLE : View.GONE)
....
}
I use a ViewGroup as a container to place multiple instances of a smaller-sized icon image at arbitrary locations. When the icons are placed near the bottom or right edge of the container, they are automatically scaled to fit within the bounds of the ViewGroup. Why does this happen, and how do I prevent it? I set the clipChildren attribute on the ViewGroup to FALSE to see if that worked, but it doesn't.
The ViewGroup is added dynamically. m_iconContainer is the ViewGroup which parents the icons.
m_outerContainer = (ViewGroup) v.findViewById( R.id.outer );
m_iconContainer = new ScalingFrameLayout( getActivity() );
m_outerContainer.addView( m_iconContainer );
Icons are added in a straightforward manner:
PinImage p = new PinImage( getActivity() );
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) p.getLayoutParams();
params.leftMargin = pt.x - PIN_IMAGE_XOFFSET;
params.topMargin = pt.y - PIN_IMAGE_YOFFSET;
m_iconContainer.addView( p );
public class ScalingFrameLayout extends FrameLayout {
private float scale = 1;
public ScalingFrameLayout(Context context) {
super(context);
setWillNotDraw(false);
setClipChildren(false);
}
public void setScale(float factor) {
scale = factor;
invalidate();
}
public float getScale() {
return scale;
}
#Override
public void onDraw(Canvas canvas) {
canvas.scale(scale, scale);
super.onDraw(canvas);
}
}
protected class PinImage extends ImageView {
public PinImage( Context c ) {
super( c );
setLayoutParams( new FrameLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT ));
setImageResource( R.drawable.droppin );
this.setOnClickListener( new OnClickListener() {
public void onClick( View v ) {
onCommentPinTouched( v );
}
});
}
}