My parent activity contains a ViewPager that hosts 3 fragments.
MainView:
<CoordinatorLayout>
<AppBarLayout/>
<ViewPager/>
</CoordinatorLayout>
In one fragment, I have a RecyclerView that displays CardViews as items
Child fragment:
<CoordinatorLayout>
<MvxSwipeRefreshLayout>
<MvxRecyclerView
local:MvxItemTemplate="#layout/child_item">
</MvxSwipeRefreshLayout>
</CoordinatorLayout>
Inside the child fragment I want each child (a CardView) to host its own RecyclerView that 1) displays its child horizontally and 2) scrollable. But the problem is, even though I have set the layoutManager and the orientation values in XML, it does not work. It displays things vertically and does not scroll.
Child_item
<CardView>
<MvxRecyclerView
local:layoutManager="android.support.v7.widget.LinearLayoutManager"
android:orientation="horizontal"
local:MvxItemTemplate="#layout/child_child"
android:background="#color/Red"/>
</CardView>
Child item inside the Cardview child_child.xml:
<LinearLayout android:background="#color/Grey">
<Button>
</LinearLayout>
Currently, the main ViewPager works fine; I can swipe to change fragments. The child fragment also supports vertical scrolling; I tried adding multiple cardviews so that it has to scroll, and I can do that just fine. Inside the cardview I added a Seekbar to see if child elements were receiving touch inputs, which also worked. Only the RecyclerView inside the card is not receiving any touch inputs, or responding to the orientation = horizontal request.
How it is currently: The "XXXXX" buttons are stacked vertically. Note that there are more than 2 "XXXX" buttons. THe cardview clipped it but ot does not scroll.
What is expected/what I'm trying to achieve: the buttons should be stacked horizontally and should be scrollable.
There is a bug in MvxRecyclerView in MvvmCross 4.4.0 that ignores XML atrributes and simply hardcodes a default LinearLayoutManager. Further developments at GitHub issue.
For the time being, for this use case, I hard-coded an MvxRecyclerView subclass to SetLayoutManager(new LinearLayoutManager(context) { Orientation = Horizontal }); and it works.
[Register("bug.droid.components.BugFixRecyclerView")]
public sealed class BugFixRecyclerView : MvxRecyclerView
{
public BugFixRecyclerView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public BugFixRecyclerView(Context context, IAttributeSet attrs) : this(context, attrs, 0, new MvxRecyclerAdapter())
{
}
public BugFixRecyclerView(Context context, IAttributeSet attrs, int defStyle) : this(context, attrs, defStyle, new MvxRecyclerAdapter())
{
}
public BugFixRecyclerView(Context context, IAttributeSet attrs, int defStyle, IMvxRecyclerAdapter adapter) : base(context, attrs, defStyle, adapter)
{
if (adapter == null)
return;
var layoutManager = new LinearLayoutManager(context) { Orientation = Horizontal };
SetLayoutManager(layoutManager);
var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId(context, attrs);
var itemTemplateSelector = MvxRecyclerViewAttributeExtensions.BuildItemTemplateSelector(context, attrs);
adapter.ItemTemplateSelector = itemTemplateSelector;
Adapter = adapter;
if (itemTemplateSelector.GetType() == typeof(MvxDefaultTemplateSelector))
ItemTemplateId = itemTemplateId;
}
}
and in the CardView's RecyclerView, replace the default MvxRecyclerView to use this subclass.
Related
I have a strange problem with the application layout and views which need to be scrolled:
If I create an application with the default App Layout, the drawer toggle is on the top and I add a VerticalLayout with fullsize as view everything is working as expected. But if the vertical content exceeded the height of the browser window (even if I put this content into a scroller), the whole view get scrolled and disappears behind the toggle until the height of the toggle is reached, than scrolling stops.
It seams that setHeight(100, Unit.Percentage) does not considers the height of the toggle.
Has someone a similiar problem?
Edit
Put the following code as a view into an AppLayout and open this on your mobile device e.g. iPhone and you will see the vertical scroller:
#Route(value = "application/test", layout = ApplicationLayout.class)
#PageTitle("Test")
#RolesAllowed({"ROLE_NEWSLETTER_ADMIN", "ROLE_ORGANIZATION_ADMIN"})
public class TestView extends VerticalLayout implements BeforeEnterObserver {
private MenuBar menu;
private Grid<Person> grid;
private Label footerLabel;
public TestView() {
this.setSizeFull();
menu = new MenuBar();
menu.addItem("New");
menu.addItem("Edit");
menu.addItem("Delete");
this.add(menu);
grid = new Grid<>(Person.class);
this.add(grid);
this.expand(grid);
footerLabel = new Label("Number of objetcs: #");
this.add(footerLabel);
}
#Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
}
}
AppLayout:
public class ApplicationLayout extends AppLayout implements AfterNavigationObserver {
private ApplicationService applicationService;
private H1 headerTitle;
public ApplicationLayout(ApplicationService applicationService) {
this.applicationService = applicationService;
createHeader();
createDrawer();
this.setPrimarySection(Section.DRAWER);
}
private void createHeader() {
// Define the header
HorizontalLayout header = new Header(new H1("Header"));
addToNavbar(header);
}
private void createDrawer() {
....
}
}
It seams that setHeight(100, Unit.Percentage) does not considers the height of the toggle.
Correct. The 100% is the size of the view port. So if you have e.g. the following
VerticalLayout vLayout = new VerticalLayout();
Button button = new Button();
button.setHeight(50, Unit.Pixels);
HorizontalLayout hLayout = new HorizontalLayout();
hLayout.setHeight(100, Unit.Percentage);
vLayout.add(button,hLayout);
This will produce something where vLayout height is more that viewport height and scroll bar emerges. Instead you should do.
VerticalLayout vLayout = new VerticalLayout();
Button button = new Button();
button.setHeight(50, Unit.Pixels);
HorizontalLayout hLayout = new HorizontalLayout();
hLayout.setHeight(200, Unit.Pixels);
vLayout.add(button,hLayout);
vLayout.expand(hLayout); // or vLayout.setFlexGrow(1.0d, hLayout);
I have my own xib-file looking like this:
Now I programmatically add multiple instances of this XIB-View to my original View which is a UIStackView with full width inside of a UIScrollView which takes the whole screen size.
I want to achieve, that every instance of my xib-view has the whole displayWidth. This is what I tried:
myView.frame.size = CGSize(myView.frame.size.height, view.frame.size.width())
But I can't change the view's width with this code. How do I achieve to change the width?
Edit: This is how I add the views (it is in java because I am using multi-os-engine but It is almost exactly like in objective-c)
#Override
#Selector("viewDidLoad")
public void viewDidLoad() {
UIView checkBox = instantiateFromNib("CheckBoxView");
for (UIView v : checkBox.subviews()) {
if (v instanceof DLRadioButton) {
((DLRadioButton) v).setTitleForState(option.getName(), UIControlState.Normal);
}
}
configView.addArrangedSubview(checkBox);
}
private UIView instantiateFromNib(String name) {
return (UIView) UINib.nibWithNibNameBundle(name, null).instantiateWithOwnerOptions(null, null).firstObject();
}
I'm new at Xamarin development and I'm trying to display the list of comments for a specific ticket. The comments' body have different text size. I have a custom view TicketCommentRowUIView.xib:
using Foundation;
using System;
using UIKit;
using ObjCRuntime;
namespace Project.iOS
{
public partial class TicketCommentRowUIView : UIView
{
public TicketCommentRowUIView(IntPtr handle) : base(handle)
{
}
public static TicketCommentRowUIView CreateView(TicketCommentModel model)
{
TicketCommentRowUIView v = CreateView();
v.LoadModelInfo(model);
return v;
}
private static TicketCommentRowUIView CreateView()
{
var arr = NSBundle.MainBundle.LoadNib("TicketCommentRowUIView", null, null);
var v = Runtime.GetNSObject<TicketCommentRowUIView>(arr.ValueAt(0));
return v;
}
private void LoadModelInfo(TicketCommentModel model)
{
DateReply.Text = model.CreatedAt.ToShortDateString();
MessageReply.Text = model.Content;
ReplierImage.Image = UIImage.FromBundle("default_image_user");
}
}
}
And I'm adding that custom view to an stack view in TicketDetailViewController dinamically, but I don't know how to resize the custom view according its self size:
using Foundation;
using System;
using UIKit;
using System.Collections.Generic;
using System.Linq;
using CoreGraphics;
namespace Project.iOS
{
public partial class TicketDetailViewController : UIViewController
{
private nfloat TicketItemHeight = 200;
TicketModel _ticketItem;
List<TicketCommentModel> _ticketCommentsList;
TicketDetailPresenter _presenter;
public TicketDetailViewController (IntPtr handle) : base (handle)
{
_presenter = new TicketDetailPresenter(this);
}
public async override void ViewDidLoad()
{
base.ViewDidLoad();
ScrollView.LoadingStart();
await _presenter.LoadTicketComments(_ticketItem.Id);
FillComentsStackView();
ScrollView.LoadingComplete();
}
public void LoadMenuItem(TicketModel ticketItem)
{
_ticketItem = ticketItem;
}
public void FillComentsStackView()
{
foreach (TicketCommentModel item in _ticketCommentsList)
{
TicketCommentRowUIView itemView = TicketCommentRowUIView.CreateView(item);
itemView.HeightAnchor.ConstraintEqualTo(TicketItemHeight).Active = true;
CommentStackView.AddArrangedSubview(itemView);
}
}
}
}
My issue is that I don't know how to add an self size for each custom view, because the height of MessageReply UILabel it is variable... I put a constraint equals to 200 but when the text is too big it looks overlapped. And when it is a short text there is to much of blank space. Is there any way to do this?? In my android project I just set WrapContent layout params and it worked. But I can't find the way to apply that to the iOS project.
It seems you have set the Label's Line 0 to wrap its content.
Then you can set each item's constraint in your xib to make your custom view self size fit. From your screenshot you can set the left, top constraint of your image and top, left constraint of your DateReply, at last the left, top, right , bottom constraint of the MessageReply like:
Adjust the constant to feed your request. Moreover when you add the custom view in the TicketDetailViewController, there's no need to set the HeightAnchor.
I'm currently developing a mobile application with JavaFX, using GluonHQ and JavaFXPorts. One of my screens contains a listview as you can see from the screenshot below, which was taken from my iPhone 6.
I have noticed the following problems with the scrollbar in mobile devices:
The first time i touch the screen the scroll bar appears a bit off place and then moves to the correct right position. This just happens quickly only the first time. (Screenshot)
I noticed that the scrollbar appears every time i touch the screen and not only when I touch and drag. On native iOS applications the scrollbar appears only when you touch and drag. If you keep your finger on screen and then remove it the scrollbar does not appear.
The scrollbar always takes some time to disappear when I remove my finger from the screen, whilst in native apps it disappears instantly.
Can anyone help me on fixing these issues. How can you define the time the scrollbar appears before it hides again?
You can experience this situation by just creating a ListView and load it with some items.
UPDATE
Thanks to the answer of Jose Pereda below, I have managed to overcome all three problems described above. Here is the code I used to reach the desired results. Watch this short video to get a quick idea of how the new scrolling bar appears and behaves. Again, Jose, you are the boss! Please go ahead with any comments for improvement.
public class ScrollBarView {
public static void changeView(ListView<?> listView) {
listView.skinProperty().addListener(new ChangeListener<Object>() {
private StackPane thumb;
private ScrollBar scrollBar;
boolean touchReleased = true, inertia = false;
#Override
public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
scrollBar = (ScrollBar) listView.lookup(".scroll-bar");
// "hide" thumb as soon as the scroll ends
listView.setOnScrollFinished(e -> {
if (thumb != null) {
touchReleased = true;
playAnimation();
} // if
});
// Fix for 1. When user touches first time, the bar is set invisible so that user cannot see it is
// placed in the wrong position.
listView.setOnTouchPressed(e -> {
if (thumb == null) {
thumb = (StackPane) scrollBar.lookup(".thumb");
thumb.setOpacity(0);
initHideBarAnimation();
} // if
});
// Try to play animation whenever an inertia scroll takes place
listView.addEventFilter(ScrollEvent.SCROLL, e -> {
inertia = e.isInertia();
playAnimation();
});
// As soon as the scrolling starts the thumb become visible.
listView.setOnScrollStarted(e -> {
sbTouchTimeline.stop();
thumb.setOpacity(1);
touchReleased = false;
});
} // changed
private Timeline sbTouchTimeline;
private KeyFrame sbTouchKF1, sbTouchKF2;
// Initialize the animation that hides the thumb when no scrolling takes place.
private void initHideBarAnimation() {
if (sbTouchTimeline == null) {
sbTouchTimeline = new Timeline();
sbTouchKF1 = new KeyFrame(Duration.millis(50), new KeyValue(thumb.opacityProperty(), 1));
sbTouchKF2 = new KeyFrame(Duration.millis(200), (e) -> inertia = false, new KeyValue(thumb.opacityProperty(), 0));
sbTouchTimeline.getKeyFrames().addAll(sbTouchKF1, sbTouchKF2);
} // if
} // initHideBarAnimation
// Play animation whenever touch is released, and when an inertia scroll is running but thumb reached its bounds.
private void playAnimation() {
if(touchReleased)
if(!inertia || (scrollBar.getValue() != 0.0 && scrollBar.getValue() != 1))
sbTouchTimeline.playFromStart();
} // playAnimation()
});
} // changeView
} // ScrollBarView
As mentioned in the comments, the first issue is known, and for now it hasn't been fixed. The problem seems to be related to the initial width of the scrollbar (20 pixels as in desktop), and then is set to 8 pixels (as in touch enabled devices), and moved to its final position with this visible shift of 12 pixels to the right.
As for the second and third issues, if you don't want to patch and build the JDK yourself, it is possible to override the default behavior, as the ScrollBar control is part of the VirtualFlow control of a ListView, and both can be found on runtime via lookups.
Once you have the control, you can play with its visibility according to your needs. The only problem with this property is that it is already bound and constantly called from the layoutChildren method.
This is quite a hacky solution, but it works for both 2) and 3):
public class BasicView extends View {
private final ListView<String> listView;
private ScrollBar scrollbar;
private StackPane thumb;
public BasicView(String name) {
super(name);
listView = new ListView<>();
// add your items
final InvalidationListener skinListener = new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
if (listView.getSkin() != null) {
listView.skinProperty().removeListener(this);
scrollbar = (ScrollBar) listView.lookup(".scroll-bar");
listView.setOnScrollFinished(e -> {
if (thumb != null) {
// "hide" thumb as soon as scroll/drag ends
thumb.setStyle("-fx-background-color: transparent;");
}
});
listView.setOnScrollStarted(e -> {
if (thumb == null) {
thumb = (StackPane) scrollbar.lookup(".thumb");
}
if (thumb != null) {
// "show" thumb again only when scroll/drag starts
thumb.setStyle("-fx-background-color: #898989;");
}
});
}
}
};
listView.skinProperty().addListener(skinListener);
setCenter(listView);
}
}
Hey, I have a stack of UIViewControllers inside of a UINavigationController. Usually the title (or the NavigationItem‘s title) decides about both the title that is displayed in the NavigationBar (displayed at the top) and all the navigation buttons, for example the back-buttons in the navigation bar itself.
Now I plan to put a bit more information into the NavigationBar‘s title, while still keeping those button labels short and concise (for example the view‘s title is “<Customer name> Overview”, while the buttons should just show “Overview”).
I am currently trying to achieve this by changing the NavigationItem‘s title on ViewWillAppear and ViewWillDisappear. This works well, but one can see the moments where the text changes (probably due to the animation). I‘ve tried different combinations with ViewDidAppear and ViewDidDisappear as well, but the effect was the best with just the Will methods. An example code for this is shown below (the Example class is pushed to a UINavigationController).
Is there a better way to achive this? Maybe with simply changing the button titles only or with just directly changing the navigation‘s title? Or can I maybe prevent the standard mechanisms from copying the title to all other instances?
public class Example : UIViewController
{
private int depth;
public Example ( int depth = 0 )
{
this.depth = depth;
this.Title = "Title";
}
public override void ViewDidLoad ()
{
base.ViewDidLoad();
UIButton btn = new UIButton( new RectangleF( 100, 100, 300, 50 ) );
btn.SetTitle( "Duplicate me!", UIControlState.Normal );
btn.TouchDown += delegate(object sender, EventArgs e) {
NavigationController.PushViewController( new Example( depth + 1 ), true );
};
View.Add( btn );
}
public override void ViewWillAppear ( bool animated )
{
base.ViewWillAppear( animated );
this.NavigationItem.Title = String.Format( "Title / {0}", depth );
}
public override void ViewWillDisappear ( bool animated )
{
base.ViewWillDisappear( animated );
this.NavigationItem.Title = "Title";
}
}
If I understand you correctly, I think there's a ready-made for this:
Set backBarButtonItem on your view controller's navigation item.