I have a listView, one component of the row is a TextView. By default, I wish to show only 2 rows of text in the TextView. But if the user taps on the TextView I wanted to the textView to expand in context.
I actually have this portion working, but I want to have a more content indicator :
My current implementation (Below) has it's own issues w/ not collapsing if the list view is scrolled, but I will handle that by storing the values for each cursor record in some collection..
I tried using chatBubble.getLineCount() but that returns zero initially b/c it has not gone through onLayout (from my understanding).
I only wish to show it if there is more than 2 lines of content.
I figure my solution will be creating my own implementation of TextView which can handle some of my requirements, but not sure if anyone has any examples I can look at.. (Or other suggestions).
<RelativeLayout
android:id="#+id/linear_layout_row_three"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/linear_layout_row_two"
android:layout_toRightOf="#+id/munzeeQuickContact"
android:orientation="horizontal" >
<TextView
android:id="#+id/chat_bubble"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:background="#drawable/chat_bubble"
android:text="I went looking for this particular token on a rainy day, and I was unable to locate it due to the bad weather, next time please leave an um I went looking for this particular munzee on a rainy day, and I was unable to locate it due to the bad weather, next time please leave an um" />
<ImageView
android:id="#+id/minimize_maximize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#id/chat_bubble"
android:layout_alignRight="#id/chat_bubble"
android:visibility="gone"
android:src="#android:drawable/ic_menu_more"/>
</RelativeLayout>
Here is some of the source I currently have :
final TextView chatBubble = (TextView) view.getTag(R.id.chat_bubble);
final ViewGroup expandableContainer = (ViewGroup) view.getTag(R.id.linear_layout_row_three);
final ImageView minimizeMaximize = (ImageView) view.getTag(R.id.minimize_maximize);
chatBubble.setOnClickListener(
new View.OnClickListener() {
boolean isExpanded = false;
int lastHeight = 0;
// This is for the auto expanding text view
#Override
public void onClick(View v) {
if (isExpanded) {
ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) expandableContainer
.getLayoutParams();
params.height = lastHeight;
chatBubble.setMaxLines(2);
expandableContainer.setLayoutParams(params);
expandableContainer.invalidate();
minimizeMaximize.setVisibility(View.VISIBLE);
} else {
lastHeight = expandableContainer.getHeight();
ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) expandableContainer
.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
chatBubble.setMaxLines(99);
expandableContainer.setLayoutParams(params);
expandableContainer.invalidate();
minimizeMaximize.setVisibility(View.VISIBLE);
}
isExpanded = !isExpanded;
}
});
I figure my solution will be creating my own implementation of
TextView which can handle some of my requirements, but not sure if
anyone has any examples I can look at..
Have a look at the class below:
public class LimitedTextView extends TextView {
private boolean mStatus;
public LimitedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Paint p = getPaint();
String s = getText().toString();
if (s != null && !s.equals("")) {
int m = (int) p.measureText(s);
if (m < getMeasuredWidth() * 2) {
modifyParent(true);
} else {
modifyParent(false);
}
}
}
private void modifyParent(boolean how) {
RelativeLayout rl = (RelativeLayout) getParent();
rl.findViewById(R.id.minimize_maximize).setVisibility(
how ? View.GONE : View.VISIBLE);
if (mStatus) {
setMaxLines(40); // arbitrary number, set it as high as possible
} else {
setMaxLines(2);
}
}
public void storeCurrentStatus(boolean status) {
mStatus = status;
}
}
The LimitedTextView will measure its text using its own Paint object and test it against the measured width. If it fits on the two allowed rows it will hide the ImageView, otherwise it will show it. It also stores the current status of row(expanded/not-expanded) and increases or decreases the maximum number of lines to obtain the proper appearance.
In the getView method of the adapter you would:
set the text
set the status from a boolean array according to a position(this is also required to keep the rows in order as you scroll the list):
textView.storeCurrentStatus(mStatus[position])
set the OnClickListener on the LimitedTextView itself and from there update the status:
mStatus[(Integer) v.getTag()] = !mStatus[(Integer) v.getTag()];
notifyDataSetChanged();
based on the same mStatus boolean array you'll probably change the drawable of the ImageView, to show a different one depending on if the TextView is expanded or not
I manually wrote it, so there could be some mistakes I'm missing right now, take it as an idea. The LimitedTextView could be improved as in performance, I also don't know how well it would behave if you want to animate expanding the text.
Related
I have problem to check, if text view been ellipsized. I defined layout for item in recycle view and I have to check, if text was ellipsized and hide button if yes. I found solution, where can I can get layout from text view and check if it was ellipsized, but in bind method in recycle view it always return false. Do you have someone some idea, how I can do it?
Layout l = textview.getLayout();
if (l != null) {
int lines = l.getLineCount();
if (lines > 0)
if (l.getEllipsisCount(lines-1) > 0)
Log.d(TAG, "Text is ellipsized");
}
This code is not working for me.
try this in your adapter
holder.textView.post(new Runnable() {
#Override
public void run() {
if (holder.textView.getLayout() != null) {
if (widthText == 0) {
widthText = holder.textView.getWidth();
}
boolean isEllipsize = !holder.textView.getText().toString().equalsIgnoreCase(holder.textView.getLayout().getText().toString());
} else {
Paint paint = new Paint();
paint.setTextSize(holder.textView.getTextSize());
final float size = paint.measureText(holder.textView.getText().toString());
boolean isEllipsize = (int) (size / maxLine) > widthText;
}
}
});
if you want to read more if text too long you can use this libs:
https://github.com/bravoborja/ReadMoreTextView
As stated in another SO post:
This only works after the layout phase, otherwise the returned layout will be null, so call this at an appropriate place in your code.
Make sure this is called after the text had been laid out (After onCreate)!
Try this,
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1" />
I have a layout which is something like this:
LinearLayout (linearLayout)
'--TextView (textView1)
'--ImageView (imageView)
'--TextView (textView2)
textView1 changes its text sometimes and it can be long, so it leaves part of textView2 out of the screen. I want to prevent that, so I want to remove imageView from the layout whenever this happens. imageView may or may not be visible at the time when this is computed (maybe it was removed before when textView1 was edited previously).
This is what I have coded:
void changeText(String veryLongString){
textView1.setText(veryLongString);
int [] loc = new int [2];
textView2.getLocationOnScreen(loc);
int bottom = textView2.getMeasuredHeight() + loc[1];
if (imageView.getVisibility() == View.GONE)
bottom += imageView.getHeight();
if (bottom > linearLayout.getMeasuredHeight()){
imageView.setVisibility(View.GONE);
} else {
imageView.setVisibility(View.VISIBLE);
}
}
But for some reason this doesn't work as expected, because it seems as if changes in the position and height of the Views don't take place immediately. When I call getMeasuredHeight() and getLocationOnScreen() I get the values BEFORE the changes I have just made. The result that I get is that if I set a very large text imageView is not removed, but if I then set a short text, it is removed.
If there any other way to face this problem?
Even though I think that this is not the right approach (you can do all kinds of stuff in your XML so you don't have to meddle with Java code), here is a quick example of what you can do from Java (for example, in your onStart() method)
ViewGroup group = (ViewGroup) findViewById(R.id.myLayout);
int groupHeight = group.getHeight();
for (int i = 0; i < group.getChildCount(); i++) {
groupHeight -= group.getChildAt(i).getHeight();
if (groupHeight < 0) {
// they don't fit in the layout
myImageView.setVisibility(View.GONE);
}
}
I'm trying to programmatically (not using XML files) create custom subviews in Android (that's what I call it in iOS) that is a basically a number of basic views (labels, buttons, text fields etc) put together into a reusable subview class so I can use it inside my UIViewControllers or Activity in Android.
I don't know what is the correct terminology in Android. There seems to be a million different terminologies.
Custom View, ViewGroups, Layouts, Widgets, Components, whatever you want to call it.
In iOS this is simply done like this:
CustomView.h
#interface CustomView : UIView
#property (nonatomic, strong) UILabel *message;
#property (nonatomic, strong) UIButton *button;
#end
CustomView.m
#implementation CustomView
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self initViews];
[self initConstraints];
}
return self;
}
-(void)initViews
{
self.message = [[UILabel alloc] init];
self.button = [[UIButton alloc] init];
[self addSubview:self.message];
[self addSubview:self.button];
}
-(void)initConstraints
{
id views = #{
#"message": self.message,
#"button": self.button
};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[message]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[button]|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[message][button]|" options:0 metrics:nil views:views]];
}
#end
Now I can reuse this custom view in any ViewController (Android Activity) I chose.
How does one achieve something like that in Android?
I've been looking around and from what I gather in Android, to add subviews, I add them to Layouts:
RelativeLayout relativeLayout = new RelativeLayout(...);
TextView textView = new TextView(...);
relativeLayout.addSubview(textView);
Does that mean I need extend RelativeLayout or ViewGroup?
Looking at this page: http://developer.android.com/reference/android/view/ViewGroup.html
It seems like we need to write some really complicated logic to layout the custom view such as:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// These keep track of the space we are using on the left and right for
// views positioned there; we need member variables so we can also use
// these for layout later.
mLeftWidth = 0;
mRightWidth = 0;
// Measurement will ultimately be computing these values.
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// Iterate through all children, measuring them and computing our dimensions
// from their size.
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
// Measure the child.
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
// Update our size information based on the layout params. Children
// that asked to be positioned on the left or right go in those gutters.
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.position == LayoutParams.POSITION_LEFT) {
mLeftWidth += Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
} else if (lp.position == LayoutParams.POSITION_RIGHT) {
mRightWidth += Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
} else {
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
}
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
}
// Total width is the maximum width of all inner children plus the gutters.
maxWidth += mLeftWidth + mRightWidth;
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Report our final dimensions.
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
All I'm trying to do is use multiple basic android labels, views, buttons in a custom view like the iOS example above, why is it so hard in Android ?
I was hoping for something simple like this:
public class CustomView extends View
{
public RelativeLayout mainLayout;
public TextView message;
public Button button;
// default constructor
public CustomView()
{
...
initViews();
initLayouts();
addViews();
}
public initViews()
{
mainLayout = new RelativeLayout(this);
message = new TextView(this);
button = new Button(this);
...
}
public initLayouts()
{
// --------------------------------------------------
// use Android layout params to position subviews
// within this custom view class
// --------------------------------------------------
}
public addViews()
{
mainLayout.addView(message);
mainLayout.addView(button);
setContentView(mainLayout);
}
}
I'm sorry I am sincerely trying to learn and build a basic Android application and not trying to bash Android's way of doing things.
I know how to add and layout subviews inside an Activity and have been doing so for the past two days but not inside a custom View/View Group/Layout. I don't want to end up constructing the exact same subview for each of my Activity in the Android app, that just goes against good coding practice right ? :D
Just need a bit of guidance here from others who have done both iOS and Android development.
Edit
It seems like what I'm looking for is called a Compound Control: http://developer.android.com/guide/topics/ui/custom-components.html
I'll keep digging and hopefully achieve the result I'm after :D
Just need to work out this Inflater business.
OK, I think I got it, not sure if it's the best solution but it does what I want.
So it goes something like this:
public class CustomView extends RelativeLayout
{
private Context context;
public TextView message;
public Button button;
public CustomView(Context context)
{
super(context);
// ---------------------------------------------------------
// store context as I like to create the views inside
// initViews() method rather than in the constructor
// ---------------------------------------------------------
this.context = context;
initViews();
initLayouts();
addViews();
}
public CustomView(Context context, AttributeSet attrs)
{
super(context, attrs);
// ---------------------------------------------------------
// store context as I like to create the views inside
// initViews() method rather than in the constructor
// ---------------------------------------------------------
this.context = context;
initViews();
initLayouts();
addViews();
}
public initViews()
{
// ----------------------------------------
// note "context" refers to this.context
// that we stored above.
// ----------------------------------------
message = new TextView(context);
...
button = new Button(context);
...
}
public initLayouts()
{
// --------------------------------------------------
// use Android layout params to position subviews
// within this custom view class
// --------------------------------------------------
message.setId(View.generateViewId());
button.setId(View.generateViewId());
RelativeLayout.LayoutParams messageLayoutParams = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
);
message.setLayoutParams(messageLayoutParams);
RelativeLayout.LayoutParams buttonLayoutParams = new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
);
button.setLayoutParams(buttonLayoutParams);
}
public addViews()
{
// adding subviews to layout
addView(message);
addView(button);
}
}
Now I can use this custom view in any of my Activity:
public class MainActivity extends ActionBarActivity {
// custom view instance
protected CustomView approvalView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
initViews();
}
public initViews()
{
...
approvalView = new CustomView(this);
approvalView.message.setText("1 + 1 = 2");
approvalView.button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("Logger", "Math formula approved! :D");
}
});
}
}
Inflater is used if we create our layout using XML which isn't something I like to do, so I generated my view's layout programmatically :D
The above "RelativeLayout" in "extends RelativeLayout" can be replace with "LinearLayout" or other layouts of course.
To add a simple answer for the general visitor to this question...
You can't add subviews to an Android View like you can with an iOS UIView.
If you need to add subviews in Android, then use one of the ViewGroup subclasses (like LinearLayout, RelativeLayout, or even your own custom subclass).
myViewGroup.addView(myView);
Android and ios app development are two different concepts, each have its own way to perform your task. Sometimes its difficult to develop a piece of code in android and sometimes in ios.
To create your view screen/design/GUI in android you can create XML file (recommended) or by code (which is somehow difficult to maintain w.r.t XML).
For your question you don't need to create your own ViewGroup or RelativeLayout or LinearLayout e.g. if you want to use RelativeLayout as a parent for your view than by using XML you can use.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Some Text"/>
</RelativeLayout>
If you want to create your view pragmatically than use
RelativeLayout parentRelativeLayout = new RelativeLayout(context);
RelativeLayout.LayoutParams parentParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
parentRelativeLayout.setLayoutParams(parentParams);
TextView childTextView = new TextView(context);
childTextView.setText("Some Text");
mRelativeLayout.addView(childTextView);
Its just a sample code both have identical output but with programmatic approach it will be difficult as your view grows.
Looking your code you are creating custom view (why?) in android we only need custom views if default views not are providing some functionally which we need to use/implement in our code.
As far as i understand you want to use custom views for reuse. Its good approach but if android is providing some functionality than why you are trying to invent wheel again, just use different layouts, use only custom views if you want something extra.
I am showing a ListView in my app with a custom item. The custom item is already doing quite a bit of work since I am using the android-swipelistview from 47deg found here.
So the "front" part of the list item is a LinearLayout with various bits nested inside it - icons on left and right and in the middle 3 different TextViews - name, address and notes. Each TextView has a different text size but the height of the whole list item is fixed so that the list looks fairly uniform,
The middle item - the address - has been causing me a bit of trouble. Basically I want to make sure it fits in and looks good. I have set space enough for it to be able to take up 2 lines, then ellipsize it after that. However if the address is very short then it all fits in one line and I have a rather large space before notes line which looks bad.
So I thought I would do some analysis on the text - if it is shorter than one line I will break it after the last comma (my addresses always have commas) so I fill both lines.
I have some code like this in my Adapter
private class MyViewHolder {
ImageView defaultLogo;
TextView name;
TextView address;
TextView notes;
ImageView otherLogo;
}
.
.
.
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
MyViewHolder holder = null;
if (convertView == null) {
.
.
holder = new MyViewHolder();
.
.
.
holder.address = (TextView) convertView.findViewById(R.id.street_address);
.
.
.
else {
holder = (MyViewHolder) convertView.getTag();
}
.
.
.
MyItem item = (MyItem) getItem(position);
// Set address:
doAfterLayout(holder.address, fixLines);
holder.address.setText(item.getAddress());
.
.
.
}
.
.
.
/**
* Runs a piece of code after the layout run
*/
public static void doAfterLayout(final View view, final FixLinesRunnable runnable) {
final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
runnable.view = view;
runnable.run();
}
};
view.getViewTreeObserver().addOnGlobalLayoutListener(listener);
}
private class FixLinesRunnable implements Runnable {
public View view;
#Override
public void run() {
// try to get the width of this control and the text
TextView tv = (TextView) view;
int lineCount = tv.getLineCount();
if (0 == lineCount) {
return;FixLinesRunnable
} else if (1 < lineCount) {
//lineCount over 2 means we leave text as it is
return;
}
//if we got here we have only one line text
//want to try to force it to be 2 lines by breaking at last comma
String text = tv.getText().toString();
int lastCommaPos = text.lastIndexOf(", ");
if (lastCommaPos > text.length() - 1) {
//comma is right at the end
lastCommaPos = text.lastIndexOf(", ",lastCommaPos);
}
if (0 < lastCommaPos) {
String secondLine = text.substring(lastCommaPos + 2);
text = text.substring(0, lastCommaPos + 1) + "\n" + secondLine;
}
tv.setText(text);
}
This Really, Nearly works. It works fine the first time the list is shown - Addresses which are too short are pushed into 2 lines, broken on the last comma. But if I scroll the item out of view and back into view it doesn't work...what could be going wrong?
The original doAfterLayout function would remove the OnGlobalLayoutListener after the first adjustment, but even with the OnGlobalLayoutListener still there, it doesn't get called a second time when the item reappears, so the text shows in one line?
Anyone got any ideas?
EDIT: Annoyingly, if something covers up the list (I have another window which pulls open from the side and covers some items) the visible items redraw... I can even see it before they are covered...
Thanks #Daniel-Benedykt for pointing me in the right direction.
In the end I subclassed TextView. The only place I found I could override which would work each time the view was shown was onDraw...so:
public class AddressTextView extends TextView {
public AddressTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw (Canvas canvas) {
int lineCount = getLineCount();
if (0 == lineCount) {
super.onDraw(canvas);
return;
} else if (1 < lineCount) {
//lineCount over 2 means we leave text as it is
super.onDraw(canvas);
return;
}
//if we got here we have only one line text
//want to try to force it to be 2 lines by breaking at last comma
String text = getText().toString();
int lastCommaPos = text.lastIndexOf(", ");
if (lastCommaPos > text.length() - 1) {
//comma is right at the end
lastCommaPos = text.lastIndexOf(", ",lastCommaPos);
}
if (0 < lastCommaPos) {
String secondLine = text.substring(lastCommaPos + 2);
text = text.substring(0, lastCommaPos + 1) + "\n" + secondLine;
}
setText(text);
super.onDraw(canvas);
}
}
Hope this helps someone.
I need to set the child view as center of the ViewPager and also I would like to show some part of the next and previous views to the current view sides(like current screen below 1). But currently the current view is starting at left side of the ViewPager(like expected screen below 2). How can I achieve that?
Here is my code..
MyViewPagerAdapter
public class MyViewPagerAdapter extends PagerAdapter {
private Activity mActivity;
private int mPageCount;
public MyViewPagerAdapter(Activity activity,int pageCount) {
mActivity = activity;
mPageCount = pageCount;
}
#Override
public int getCount() {
return mPageCount;
}
#Override
public boolean isViewFromObject(View view, Object obj) {
return (view ==(View)obj);
}
#Override
public Object instantiateItem(ViewGroup container,final int position) {
ViewGroup viewGroup = (ViewGroup)mActivity.getLayoutInflater().inflate(
R.layout.item_view, null);
viewGroup.setBackgroundColor(randomColor());
TextView textView = (TextView)viewGroup.findViewById(R.id.textView1);
textView.setText("Page: "+(position+1));
Button button = (Button) viewGroup.findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(mActivity, "Hey, Its clicked!!! at page "+(position+1), Toast.LENGTH_LONG).show();
}
});
container.addView(viewGroup);
return viewGroup;
}
Random rnd = new Random();
private int randomColor(){
return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
//must be overridden else throws exception as not overridden.
Log.d("Tag", collection.getChildCount()+"");
collection.removeView((View) view);
}
#Override
public float getPageWidth(int position) {
return 0.8f;
}
}
MainActivity
public class MainActivity extends Activity {
private ViewPager viewPager;
LinearLayout linearLayout;
private int ID = 100;
private final int count = 8;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.viewPager);
linearLayout = (LinearLayout) findViewById(R.id.indicator_layout);
generateIndicators(count);
MyViewPagerAdapter adapter = new MyViewPagerAdapter(this, count);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
int oldPosition = 0;
#Override
public void onPageSelected(int position) {
//this changes the old position's view state image
((TextView)linearLayout.getChildAt(oldPosition)).setText("");
oldPosition = position;
//this changes the current position's view state image
((TextView)linearLayout.getChildAt(position)).setText((position+1)+"");
}
//this method will be called repeatedly upto another item comes as front one(active one)
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
//this will be called as per scroll state
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
viewPager.setOffscreenPageLimit(4);
}
private void generateIndicators(int count) {
/// Converts 14 dip into its equivalent px
int padd = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics());
for(int i=0;i<count;i++){
TextView textView = new TextView(this);
textView.setId(ID+i);
final int currentItem = i;
textView.setBackgroundResource(R.drawable.white_cell);
textView.setPadding(padd,padd,padd,padd);
/// Converts 14 dip into its equivalent px
int size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics());
textView.setTextSize(size);
textView.setGravity(Gravity.CENTER);
/// Converts 14 dip into its equivalent px
int px = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(px, px);
linearLayout.addView(textView,params);
}
((TextView)linearLayout.getChildAt(0)).setText("1");
}
}
activity_main.xml
<RelativeLayout 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"
tools:context=".MainActivity" >
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
</android.support.v4.view.ViewPager>
<LinearLayout
android:id="#+id/indicator_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="19dp" >
</LinearLayout>
</RelativeLayout>
item_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="match_parent"
android:id="#+id/root_view"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="click me" />
</LinearLayout>
Current screen
expected screen
For one app I implemented similar the following way, with standard ViewPager:
Make pages full-screen with the actual content in an inner layout. For example, make the full-screen layout a RelativeLayout with transparent background and the actual content another RelativeLayout centered in the parent. If I remember right, the reason for this was that with just the inner layout as a page, the ViewPager would not have taken all the screen width on some devices such as Galaxy Nexus.
Use ViewPager.setPageMargin() to set up a negative page margin i.e. how much of the next/previous page you want to show. Make sure it only overlaps the transparent region of the parent full-screen layout.
Call ViewPager.setOffscreenPageLimit() to adjust the off-screen page count to at least 2 from the default 1 to ensure smooth paging by really creating the pages off-screen. Otherwise you will see next/previous pages being drawn while already partially showing on screen.
For anyone upset that the OP didn't update his question with the solution here is a link that explains, with minimal effort, how to pull this off in XML: http://blog.neteril.org/blog/2013/10/14/android-tip-viewpager-with-protruding-children/
Basically when you declare your viewpager in XML, give it the same left and right padding and set android:clipToPadding="false". (The clipToPadding is missing in his xml sample and necessary to achieve this effect)
Finally, I have added my solution for this question in GitHub. I have done some pretty tricks to get the workaround solution. You can get the project from the below link(Actually I have planned to create a blog with the explanation , but I dint have that much time to do).
Here is the link(https://github.com/noundla/Sunny_Projects/tree/master/CenterLockViewPager)
You have to copy the files from com.noundla.centerviewpagersample.comps package to your project. And you can see the usage of that Viewpager in MainActivity class.
Please let me know if anyone has problems with this.
I found solution in this post, below the code i used:
// Offset between sibling pages in dp
int pageOffset = 20;
// Visible part of sibling pages at the edges in dp
int sidePageVisibleWidth = 10;
// Horizontal padding will be
int horPadding = pageOffset + sidePageVisibleWidth;
// Apply parameters
viewPager.setClipToPadding(false);
viewPager.setPageMargin(UIUtil.dpToPx(pageOffset, getContext()));
viewPager.setPadding(UIUtil.dpToPx(horPadding, getContext()), 0, UIUtil.dpToPx(horPadding, getContext()), 0);
dpToPx code:
public static int dpToPx(int dp, Context context) {
float density = context.getResources().getDisplayMetrics().density;
return Math.round((float) dp * density);
}
This is all you need
You can use padding for viewPager and set clipToPadding false
Java
viewPager.setClipToPadding(false);
viewPager.setPadding(50, 0, 50, 0);
Kotlin
viewPager.clipToPadding = false
viewPager.setPadding(50, 0, 50, 0)
I had to center current page in view pager with different page widths, so solution with paddings was not suitable. Also user scrolling was disabled (it was tab bar view pager, scrolled by another view pager). Here is a very simple solution to do that - just override ViewPager.ScrollTo method just like this (C# code, Xamarin):
public override void ScrollTo(int x, int y)
{
x -= (int) (MeasuredWidth * (1 - Adapter.GetPageWidth(CurrentItem)) / 2);
base.ScrollTo(x, y);
}
And if you calculate page width for each fragment don't forget to cache them in array.
Extend HorizontalScrollView class as the parent for the scrolling view. In the onMeasure() method you can specify the width and height of each child. Little cumbersome way but the effect will be good and you can have a good hold on your child view.