Good day everyone.
I am creating a calendar component, and I'm working in the month view. I have created a view named MonthView, and I am adding a couple instances of this to a ViewFlipper:
viewFlipper = new ViewFlipper(getContext());
viewFlipper.addView(new MonthView(viewFlipper.getContext()));
viewFlipper.addView(new MonthView(viewFlipper.getContext()));
I have implemented the fling gesture so that I change views when sliding my finger left or right. This will cyclically update and display the months.
Now, I need to give the fling gesture a smoothly effect when touching and slowly sliding my finger. The same we get when we use a Slider instead a ViewFlipper.
The problem with Scroller is that the effect is not cyclic. Once I get to the last view, I have to slide in the other direction.
I need someone help me find how to give a scroll-like effect to the ViewFlipper, or how to make a Scroller cyclic.
Thanks in advance.
Extra comment:
I have already implemented a ViewFlipper with 2 views. I update the views by using the SimpleOnGestureListener.onFling(...) method, and the behavior I got is something like this:
Imagine I always slide from rigth to left, like flipping a book's page to read the next one, and also imagine there is a caption in the header of the view that is displayed after flipping.
View # 0 --> Caption: January 2011
View # 1 --> Caption: Febrary 2011
View # 0 --> Caption: March 2011
View # 1 --> Caption: April 2011
View # 0 --> Caption: May 2011
If at this point I slide from left to right, the result will be something like:
View # 1 --> Caption: April 2011
View # 0 --> Caption: March 2011
The ability to cyclically move forward or backward, giving the user the idea of having infinite views, but using only a couple is characteristic of ViewFlipper, and that's what I can't loose.
That's why I need a way to add the cool scroll effect without loosing what I've got.
Thanks.
Then you can use ViewFlinger!
viewflinger this an android widget (extends ViewGroup) that allows to group a set of views that can be swiped horizontally. It offers a smoother transition that cannot be accomplished using ViewFlipper. This is based on the Workspace class on the iosched project, which is based in the class with the same name of the Launcher app.
Download: 1.0.2 | Sources | JavaDoc
If you use Maven, you can use it as an artifact from this repository: http://mvn.egoclean.com/. Also, you would want to look this video where I show how it looks like: http://www.youtube.com/watch?v=gqIXq5x7iLs (sorry for my accent which sucks)
I think what you wanted to do is create an apparently infinite list of layouts being flinged by either the ViewFlipper or Christian's ViewFlinger. And also you want to keep reusing views / layouts inside the Flinger / Flipper. Right ?
If yes, probably the following is what you wanted to do. I've done this based on Christian's ViewFlinger,
Here you go,
First add three layouts to the ViewFlinger:
<com.egoclean.android.widget.flinger.ViewFlinger
android:id="#+id/calendarViewFlipper"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ScrollView
android:id="#+id/calendarViewLayout0"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</ScrollView>
<ScrollView
android:id="#+id/calendarViewLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</ScrollView>
<ScrollView
android:id="#+id/calendarViewLayout2"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</ScrollView>
</com.egoclean.android.widget.flinger.ViewFlinger>
Then inside your activity, you take an array of three views so that you can access them directly through the array instead of searching every time inside the flinger,
private ViewFlinger viewFlinger;
private ViewGroup layouts[] = new ViewGroup[3];
private boolean userEvent = false;
#Override
public final void onCreateSub(Bundle savedInstanceState)
{
setContentView(R.layout.your_layout);
viewFlinger = (ViewFlinger) findViewById(R.id.calendarViewFlipper);
layouts[0] = (ViewGroup) findViewById(R.id.calendarViewLayout0);
layouts[1] = (ViewGroup) findViewById(R.id.calendarViewLayout1);
layouts[2] = (ViewGroup) findViewById(R.id.calendarViewLayout2);
viewFlinger.setOnScreenChangeListener(new ViewFlinger.OnScreenChangeListener()
{
#Override
public void onScreenChanging(View newScreen, int newScreenIndex)
{
}
#Override
public void onScreenChanged(View newScreen, int newScreenIndex)
{
if (userEvent)
{
ViewGroup tempLayout = null;
if (newScreenIndex != 1)
{
// We don't want our actions to raise events and create a cyclic event chain
userEvent = false;
if (newScreenIndex == 2) // Scrolling towards right
{
tempLayout = layouts[0];
viewFlinger.removeViewFromFront();
viewFlinger.addViewToBack(tempLayout);
layouts[0] = layouts[1];
layouts[1] = layouts[2];
layouts[2] = tempLayout;
// Any other logic comes here...
}
else if (newScreenIndex == 0) // Scrolling towards left
{
tempLayout = layouts[2];
viewFlinger.removeViewFromBack();
viewFlinger.addViewToFront(tempLayout);
layouts[2] = layouts[1];
layouts[1] = layouts[0];
layouts[0] = tempLayout;
// Any other logic comes here...
}
// We switch the screen index back to 1 since the current screen index would change back to 1
viewFlinger.setCurrentScreenNow(1, false);
userEvent = true;
// And any other logic that you'd like to put when the swapping is complete May be fill the swapped view with the correct values based on its new location etc...
View result = refreshView(tempLayout.getChildAt(0));
if (result.getParent() != tempLayout)
{
((ViewGroup) result.getParent()).removeView(result);
tempLayout.removeAllViews();
tempLayout.addView(result);
}
}
}
}
});
}
I hope this is clear to you and helps you with your problem. It is working very fine for me! Should work fine for you too.
P.S. Thanks # Christian for the ViewFlinger, it is awesome. However it lacks some good onConfigurationChanged logic, if you get time do put something in :). The rest is the best !
Related
I am facing a strange behaviour with a RecyclerView as a second child of CoordinatorLayout, just after an AppBarLayout (as described in a lot of examples).
My problem is when I scroll the recycler view and I want to click on a particular item. Sometimes I need to click 2 times to select that item, it seems to be linked to the fling behaviour. For example, if I scrolled to the bottom of the recycler view, then if I fling my finger from the bottom of the screen to the top (in order to see more data, but in my case I can't see more data since I am already to the bottom) and then quickly click on an item, it seems to stop the fling, and the second click actually select the item...
This behaviour is clearly not happening when using a simple recycler view without CoordinatorLayout.
My recyclerview is just holding a simple list of String, and using the following layout behaviour : #string/appbar_scrolling_view_behavior
Do you have any idea why ?
[EDIT]
I just tried with the Android Studio sample Scrolling Activity, and it looks like it is a bug from Google support repository.
In fact, when using support version 26.1.O (same with 26.0.0 and 26.0.2), the bug I am talking about is present, but if you try with the version 26.0.0-alpha1 or 26.0.0-beta1, it is actually working...
There is two open bugs at Google about this :
https://issuetracker.google.com/u/1/issues/66996774
https://issuetracker.google.com/u/1/issues/68077101
Please star these bugs if you are facing the same problem
Google just posted a workaround for this bug, it will be publicly released later.
https://gist.github.com/chrisbanes/8391b5adb9ee42180893300850ed02f2
If Using RecyclerView in NestedScrollView add this line to RecyclerView :
android:nestedScrollingEnabled="false"
I hope it help you.
I also found this problem ... after wasting so many hours searching and trying different things, I came out with a trick, its not pretty but it could work for someone else too.
Basically the idea is simulate a click on the nestedScrollView.
In my case after I detect the 'AppBarLayout' its fully expanded, I send a tap to the nested.
protected void onCreate(final Bundle savedInstanceState) {
getAppBarLayout().addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) {
if (verticalOffset == 0) {
// State.EXPANDED
simulatedClick(nestedScroll)
} else if (Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()) {
// State.COLLAPSED
} else {
// State.IDLE
}
}
});
}
private void simulatedClick(#NonNull final View view) {
// Obtain MotionEvent object
final long downTime = SystemClock.uptimeMillis();
final long eventTime = SystemClock.uptimeMillis() + 100;
final MotionEvent motionEvent = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, 0.0f, 0.0f, 0);
// Dispatch touch event to view
view.dispatchTouchEvent(motionEvent);
}
NOTE: I don't really recommend the use of hacks like this, it's unprofessional and unmaintainable, but the more you know...
I took the source I found here:
https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/Renderers/MasterDetailRenderer.cs
And created a custom renderer in a Xamarin Forms Android project. I got the project to build and the menu to open / close as expected as well as display the starting detail page.
The detail page is a "NavigaionPage" in Xamarin.Forms which is actually just a ViewGroup with a bunch of code to make it work with Push / Pop functions.
When I push a new Page, one that has a custom renderer and native RelativeLayout as a subview, the Page appears blank white until I rotate orientation.
After some research in the code, I realized both OnMeasure and OnLayout was not being called in the Page which was being pushed to the NavigationPage / detail page (with the pushed page's size stuck at 0x0 until orientation change).
I created another custom renderer but this time for the NavigationPage which looks like this:
public class MasterDetailContentNavigationPageRenderer : NavigationPageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
if(e.OldElement != null)
{
e.OldElement.Pushed -= handleOnPushed;
}
if(e.NewElement != null)
{
e.NewElement.Pushed += handleOnPushed;
}
}
private void handleOnPushed(object sender, EventArgs e)
{
var w = MeasureSpec.MakeMeasureSpec(MeasuredWidth, MeasureSpecMode.Exactly);
var h = MeasureSpec.MakeMeasureSpec(MeasuredHeight, MeasureSpecMode.Exactly);
Measure(w, h);
Layout(Left, Top, Right, Bottom);
}
}
This custom renderer for the detail page / NavigaionPage worked for 2 out of 3 of my pushed Pages.
The main / most important page did not fully add all of the UI elements.
The Page that was pushed and missing UI has SOME of the UI. The following is a list of how the page is displayed:
--- SongViewerLayout : RelativeLayout
------ RelativeLayout
--------- ViewerTopBar (draws all buttons except one)
--------- DocumentView (blank white spot between top bar and pagination)
--------- ViewerPagination (draws background color but not page buttons)
If I change orientations the missing button appears in the top bar, the page numbers show up and the document view sort of draws but it's off.
If I go back to using Xamarin.Forms provided MasterDetailPage the UI just loads as expected.
I have studied the code in the Xamarin.Forms repo but none of it really applies to the inner workings of the elements that are added. In other words, the detail page (which in my case is a NavigationPage) gets added on SetElement(...) and some handlers are assigned ... but nothing I could tell that looks for Pages being pushed and then responding. I would assume / expect the NavigationPage just to work as expected now that it's a subview of the MasterDetailPage / DrawerLayout.
Note: the views that seems to have the most issues are added programmatically and given "LayoutParamaters". The views found in the AXML seem to just show up.
Is there something I am missing to "initialize" a layout in Android? I have made native apps in Android and not had to do anything special.
Why would the UI above the NavigationPage cause the NavigationPage not to do standard initialization?
What am I missing???
Edit:
I created a working example of this issue on my GitHub account which you can clone here:
https://github.com/XamarinMonkey/CustomMasterDetail
Build and run CustomMasterDetail.Droid (Notice the UI is a MasterDetailPage)
Tap any item
The bottom blue bar has no page numbers.
Rotate orientation the page numbers display.
Stop the build
Comment out the entire class "MainMasterDetailPageRenderer.cs"
Rebuild / run the app
Tap any item
Page numbers are shown right away as expected just because it is now using the default MasterDetailPage!
Give suggestions please!
Edit #2:
Thanks to some feedback, it does not seem to be an issue in 6.0 devices. My testing device is a "Samsung Galaxy Tab Pro" with v5.1.1. And unfortunately I need this app to go back to 4!
Edit #3:
The issue has to be adding a view programmatically in Android 5.1.1 to a native RelativeLayout (which is pushed to a NavigationPage detail page ) after it is inflated in Xamarin.Forms. Calling Measure / Layout manually seems to solve the issue. But I need a more automated solution.
I came up with a solution that works. It might be expensive but it seems to layout fast on devices I have tested.
Basicly, I read up on Android and found a way to listen for layout changes using the ViewTreeObserver.GlobalLayout event combined with calling Measure / Layout on the detail and master ViewGroups.
public void MeasureAndLayoutNative()
{
if(_childView != null)
{
IVisualElementRenderer renderer = Platform.GetRenderer(_childView);
if(renderer.ViewGroup != null)
{
var nativeView = renderer.ViewGroup;
var w = MeasureSpec.MakeMeasureSpec(nativeView.MeasuredWidth, MeasureSpecMode.Exactly);
var h = MeasureSpec.MakeMeasureSpec(nativeView.MeasuredHeight, MeasureSpecMode.Exactly);
nativeView.Measure(w, h);
nativeView.Layout(nativeView.Left, nativeView.Top, nativeView.Right, nativeView.Bottom);
}
}
}
I updated my code example above with the working changes!
I have an activity A. I am creating a kind of tutorial for user for this activity, to teach him how he can use the app on that screen.
For that, my requirement is :
I want to blur all the views of the activity except one view. I want to prompt user to click on that view through a hand image pointing at that view.
Nothing should happen if the user clicks on the blurred/greyed out area, but if he taps on that particular active view, it should react to that touch.
I was thinking of using a full screen fragment for this. The Fragment will take the following input from the activity :
for what coordinates, is should not blur the screen and pass the touch event to the activity
the coordinates on which it should show that pointing hand image.
After from these coordinates, the fragment background would be blur.
I wanted to confirm if that's possible, to make the fragment partially active, i.e. delegate it's touch events to the activity for a particular view of the activity.
Also, please let me know if there is any other better approach of achieving the same thing.
Edit1 :
Thinking of using a fragment here, because I'd want this type of behaviour on different screen in future. In that case, I'd make that fragment generic which takes some inputs (as described above) and use it on different screens.
There's a very good library called SCV which does what you're trying to achieve, you're able to customize the styles for it too. I've used this for first time the app is opened to show the user a tutorial.
According to their Github
The ShowcaseView (SCV) library is designed to highlight and showcase specific parts of apps to the user with a distinctive and attractive overlay. This library is great for pointing out points of interest for users, gestures, or obscure but useful items.
Further Reading:
Android Arsenal - Showcase Views Tutorial
ShowCaseView on Android - Indipendev
I found it much easier to include an 'extra' layout around the UI of my activity, and then to add a highest-z grey mostly-transparent filter to it and put the instructions on that.
Each "step" of the instructions was a different layout that was dynamically loaded into that layout container as they clicked. (Just another approach)
The 'container' layout is a: FrameLayout
then in my Activity I have: (ignore bad naming)
private void addOverlayLayout() {
frameLayout = (FrameLayout) findViewById(R.id.framelayoutInner);
frameLayout3 = (FrameLayout) findViewById(R.id.framelayout3);
frameLayout3.setBackgroundColor(Color.DKGRAY);
frameLayout3.setAlpha(0.3f);
// Dynamically create a relativelayout which will be appended to framelayout
relativeLayout = new RelativeLayout(getApplicationContext());
relativeLayout.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams
.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
instructionOverlays.add(createSimpleClickInstruction(R.layout.instruction_reader_1));
instructionOverlays.add(createSimpleClickInstruction(R.layout.instruction_reader_2));
if (FullscreenReaderActivity.isFirstRun) {
displayNextGuide();
}
}
public void displayNextGuide() {
// clean relative layout if it has views
relativeLayout.removeAllViews();
// clean frame layout if it has child (safe if empty)
frameLayout.removeView(relativeLayout);
if (!isFirstRun) {
return;
}
if (instructionOverlays.size() > 0) {
runOnUiThread(instructionOverlays.get(0));
instructionOverlays.remove(0);
} else {
frameLayout3.setBackgroundColor(Color.TRANSPARENT);
frameLayout3.setAlpha(1.0f);
}
}
public Runnable createSimpleClickInstruction(final int resource) {
return new Runnable() {
#Override
public void run() {
getLayoutInflater().inflate(
resource,
relativeLayout,
true
);
relativeLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
displayNextGuide();
}
});
frameLayout.addView(relativeLayout);
}
};
}
I have 2 MyGameScreen objects that extends cocos2d::CCLayer. I am capturing the ccTouchesMove of the first screen so that I can create the moving effect exactly like sliding between pages of iOS application screen.
My class is like so:
class MyGameScreen: public cocos2d::CCLayer {
cocos2d::CCLayer* m_pNextScreen;
}
bool MyGameScreen::init() {
m_pNextScreen = MyOtherScreen::create();
}
void MyGameScreen::ccTouchesMoved(CCSet *touches, CCEvent *event){
// it crashes here... on the setPosition... m_pNextScreen is valid pointer though I am not sure that MyOtherScreen::create() is all I need to do...
m_pNextScreen->setPosition( CCPointMake( (fMoveTo - (2*fScreenHalfWidth)), 0.0f ) );
}
EDIT: adding clear question
It crashed when I try to setPosition on m_pNextScreen...
I have no idea why it crashed as m_pNextScreen is a valid pointer and is properly initialized. Could anybody explain why?
EDIT: adding progress report
I remodelled the whole system and make a class CContainerLayer : public cocos2d::CCLayer that contains both MyGameScreen and MyOtherScreen side by side. However, this looked like not an efficient approach, as when it grows I may need to have more than 2 pages scrollable side by side, I'd prefer to load the next page only when it is needed rather than the entire CContainerLayer that contains all the upcoming pages whether the user will scroll there or not... Do you have any better idea or github open source sample that does this?
Thank you very much for your input!
Use paging enable scrollview.download files from following link and place in your cocos2d/extenision/gui/ after that you have to set property of scrollview to enablepaging true with paging view size.
https://github.com/shauket/paging-scrollview
For Scene Transitions you can do this:
void MyGameScreen::ccTouchesMoved(CCSet *touches, CCEvent *event)
{
CCScene* MyOtherScene = CCTransitionFadeUp::create(0.2f, MyOtherScreen::scene());
CCDirector::sharedDirector()->replaceScene(MyOtherScene);
}
I am writing Instrumentation tests for my android app. One thing I want to do is to find whether all the UI components are on the screen or not. For that I have taken the screen shot of the complete screen and then I am looking for a particular widget in that image.
This code need to be running on the device only, not on desktop.
E.g. the full screen shot (image-1) have various android components like textview, button, listview and a image. Now I have a subset of this image (image-2), suppose the image of the button.
How can I find that whether image-2 is part of image-1?
Assuming that this code is happening from within the application, it doesn't seem like image comparison is the easiest way to determine whether a view is visible.
If you are writing an external instrumentation application of some sort, and this answer doesn't apply, please let me know.
Here's what I would do to test for the presence of UI elements from within the app:
From the Android API docs on the View object: you can find a view by its ID that was set up in the XML file:
<Button
android:id="#+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/my_button_text"/>
In the App:
Button myButton = (Button) findViewById(R.id.my_button);
Then, check the getVisibility() and getGlobalVisibleRect (Rect r, Point globalOffset), both documented on the View doc page.
Pseudocode:
int[] viewIds = {<known ids from xml>};
foreach(int viewId in viewIds) {
View v = findViewById(viewId);
if (v!=null) {
bool isVisible = (v.getVisibility()==VISIBLE) && getGlobalVisibleRect(new Rect(), new Point());
// do something with the visible/invisible info
}
}