How would I go about creating a custom container view in Android? - java

I would like to create a view with a listener that acts sort of like a container or grocery bag. Any image or text that is dragged into it, gets collected and saved into a local database. Or rather the image name or string gets saved. I've created custom views before that displayed a custom row of data but I'm not sure how to create a "grocery bag" type of view. I've searched Google for creating a custom container view with listener but couldn't find anything related to what I am looking for. I'm not asking anyone to do it for me, just give me some advice or push in the right direction.
Edit
Just to clarify a little more. I already know how to drag and drop a view. The problem with that is that you can drop anywhere. What I want is a view that when something is dropped within its bounds, it gets the views' string or tag. Regardless of what kind of view that may be. Somehow this custom view has to know what kind of view was dropped within its bounds and remove that view on drop.

<view class="at.calista.quatscha.views.SwipebarLayout"
android:id="#+id/sbl"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<!-- Top View -->
<Button android:text="Top" android:id="#+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
<!-- Bottom View -->
<Button android:text="Bottom" android:id="#+id/Button02" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

You would need a method that determines whether two views overlap. For example, something like these methods could work for when the target view and the dragged item view are 2 distinct views in the same container view:
private Rect getScreenBounds(View view)
{
int[] location = new int[2];
view.getLocationOnScreen(location);
return new Rect(location[0], location[1], location[0] + view.getWidth(), location[1] + view.getHeight());
}
private boolean doViewsIntersect(View dropTarget, View item)
{
Rect dropRect = getScreenBounds(dropTarget);
Rect itemRect = getScreenBounds(item);
return Rect.intersects(dropRect, itemRect);
}
However, if the views being dragged are child views on a container, and that container has a special hit area where you want to perform the drop logic, then you can just use that hit area's Rect for the intersect test.
To save information on the View, you have a few options:
You can use view.setTag(key, object) and view.getTag(key) to store whatever information you'd like on each item.
If you are using a custom class for the items, you can just add a method to get the data from the view class.
If you are using multiple different classes for the items, you could make an interface, and implement that interface on each draggable view class.
On an item's release, you could then check whether the areas/views intersect, and if they do, get whatever information you need from the dragged item to process as necessary, either using getTag or the method added to the view.
For example, this could be triggered via an onTouchListener, when you receive a MotionEvent.ACTION_UP event. Here's an example of a listener that you can add on each item:
item.setOnTouchListener(new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
if (event.getActionMasked() == MotionEvent.ACTION_UP)
{
if (doViewsIntesect(dropTarget, v))
{
Object data = v.getTag(DATA_KEY);
//process data
((ViewGroup) v.getParent()).removeView(v); // to remove the item afterwards
}
}
return false;
}
});

Related

Android - RecyclerView with one layout, multiple setVisibility

I have a basically all in one layout which has everything needed for my app's main feed. All variable items (images, video thumbnails.. Etc.) are set to GONE at first and set to VISIBLE when it is needed.
The problem is sometimes, might be due to RecyclerView's recycling behavior, the item which is supposedto be GONE is VISIBLE in the wrong places.
Example :
Item no 1 contains Text
Item no 2 contains Image
Item no 3 contains Image
I keep scrolling down to item no x, then scroll back up and here's what I get :
Item no 1 contains Image from item no x, sometimes item no 3
Item no 2 contains Image
Item no 3 contains Image
I'm using a custom ViewHolder which extends RecyclerView.ViewHolder.
The purpose of the CustomViewHolder is for layout declaration and initialization.
ProgressBar progressBar;
View viewDimmer;
RelativeLayout postListWrapper;
...
public ObjectViewHolder(View v) {
super(v);
progressBar = (ProgressBar)v.findViewById(R.id.post_inscroll_progressBar);
viewDimmer = (View)v.findViewById(R.id.post_inscroll_viewDimmer);
postListWrapper = (RelativeLayout)v.findViewById(R.id.post_inscroll_postListWrapper);
}
An example of how I load the image :
Picasso.with(context)
.load(youtubeThumbnailUrl)
.fit()
.centerCrop()
.into(
((ObjectViewHolder) holder).userPostYoutubeImage
);
I've set each visibility to GONE if no url is obtained from the server
((ObjectViewHolder) holder).userPostImageWrapper.setVisibility(View.GONE);
((ObjectViewHolder) holder).userPostYoutubeImageWrapper.setVisibility(View.GONE);
But somehow the image is still reused on the previous items (yes, not only Item no 1). Sometimes image are also in the wrong ImageView. Image D is supposed to be in ImageView D, but it's in ImageView A instead.
Any guide for setting RecyclerView up and going nicely?
If I miss anything, or need to supply more code, please do inform me :D
You need to put the else condition too. Like the example below.
// if no url is found from server
if(url == null){
((ObjectViewHolder) holder).userPostImageWrapper.setVisibility(View.GONE);
((ObjectViewHolder) holder).userPostYoutubeImageWrapper.setVisibility(View.GONE);
} else {
// Some url has found
((ObjectViewHolder) holder).userPostImageWrapper.setVisibility(View.VISIBLE);
((ObjectViewHolder) holder).userPostYoutubeImageWrapper.setVisibility(View.VISIBLE);
}
Do this for each of the items you've got there as the list item in case of you're setting their visibilities in runtime.
All your if conditions in onBindViewHolder() must have an else block too.
Don't leave any if condition without else. You can provide a default behaviour in the else block when if condition becomes false.

Set Canvas to Eclipse View?

I've created an (empty until now) View in my Eclipse Plugin. I'm also using Eclipse Cloudio. This library provides the following object (description taken from the linked site):
A TagCloud is a special org.eclipse.swt.widgets.Canvas, dedicated to display a tag cloud.
Basically it's an image showing a WordCloud. Now there are snippets on the site on how to show such TagClouds in a shell / popup.
But I'd like to show them in a view (this function is supposed to be used often and I think it's bad style to spam popup-windows).
What I do not know tho is how to set this TagCloud (which is a Canvas) to the View / make the View display the Canvas. Maybe someone can help me with this?
Edit:
gregs Answer works like a charm! It just needs another setWords() function which is called from where-ever which contains .setWords to set the words when neccessary.
You just need to add the control to the view in the view createPartControl. At the simplest that would be:
#Override
public void createPartControl(final Composite parent)
{
TagCloud cloud = new TagCloud(parent, SWT.NONE);
... set up the cloud as in the example
}

Start an activity with a shared element from custom view

I want to create a animation between two Activities with a image as shared element, see Customize Activity Transitions
My problem: In the source activity the image is drawn on a canvas of a custom view :-(
Is there a way to use this image as a shared element or do I have to add a real ImageView?
You can't share the image only, but you can share the entire custom view. This means that the entire custom View would disappear from the calling Activity when the shared element is transferred to the launched Activity. If your custom View only has the image, that would be fine, but if it paints other things, that would be disastrous.
If you want to share only the image, you'll have to create a View (e.g. ImageView) and move the image to it and then share it. That way, when the shared element is transferred, it hides properly from the calling activity.
The shared elements don't actually move Views between Activities, they just share a 'snapshot' of the view as a bitmap and the position of the view. In the launched activity, the view with the given transition name will be laid out in that position. You can use the snapshot or not, depending on your needs. By default, the snapshot is not used.
So you'll need some code like this:
public void launchActivity(final Intent intent, final CustomView view) {
final Bitmap bitmap = view.getSharedImage();
ImageView imageView = new ImageView(view.getContext());
imageView.setBitmap(bitmap);
LayoutParams layoutParams = view.createSharedImageLayoutParams();
final ViewGroup parent = (ViewGroup)view.getParent();
parent.addView(imageView, layoutParams);
parent.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
parent.getViewTreeObserver().removeOnPreDrawListener(this);
customView.hideSharedImage();
ActivityOptions activityOptions = ActivityOptions.
makeSceneTransitionAnimation(this, imageView, "destName");
startActivity(intent, activityOptions.toBundle();
}
});
setExitSharedElementCallback(new SharedElementCallback() {
#Override
public void onSharedElementsArrived(List<String> sharedElementNames,
List<View> sharedElements, OnSharedElementsReadyListener listener) {
super.onSharedElementsArrived(sharedElementNames, sharedElements,
listener);
parent.removeView(imageView);
customView.showSharedImage();
}
});
}
I haven't specifically tried the above, but that's the essence of it. If you don't want to use the newer onSharedElementsArrived, you can create a custom ImageView that listens for onVisibilityChanged. If you have an exit transition, you can also listen for the end of it as well. You just need some trigger that will tell you to reset the state so that the ImageView is removed and your custom View should draw the image again.
In the example above, I placed the ImageView in the same parent as the custom View. You may get more flexibility by putting it into the DecorView, but you'll have to figure out what the global position is and it will also overlay everything on the screen. Alternatively, since I added the ImageView to the parent, that won't work for all parents (e.g. ListView, LinearLayout). You know your View hierarchy and you'll have to choose the best place to put it.
Or, you could change your custom View to be a custom ViewGroup and contain the sharable image as an ImageView! Sounds easier to me.

Portrait layout doesnt work when activity starts in landscape

This is a scaled down version of the actual problem. To recreate the difficulty I am facing.
I have taken the example from the official website developer.android.com to cite my problem.
Building a Flexible UI
MainActivity has 2 layouts. One is for the default(portrait in small screen devices) layout in the layout folder. The other layout for both large-screen and landscape mode, kept in layout-large and layout-land folder.
Default layout for activity_main.xml contains only one FrameLayout (R.id.fragment_container) in which I add and replace 2 fragments that I create, dynamically.
The other layout is same for both the layout-land and layout-large folders. It has 2 static fragments [R.id.headlines_fragment - to display a list of headlines] and [R.id.article_fragment - to display the details when headlines are selected]. Horizontally placed. One on the left to show the lists and the one on the right to show details.
This is the code for MainActivity.java which controls all the fragments :
public class MainActivity extends Activity implements OnHeadLineSelectedListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_article);
if(findViewById(R.id.fragment_container) != null) {
if(savedInstanceState != null) {
return;
}
HeadlinesFragment firstFragment = new HeadlinesFragment();
firstFragment.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(R.id.fragment_container, firstFragment).commit();
}
}
#Override
public void onArticleSelected(int position) {
ArticleFragment articleFrag = (ArticleFragment) getFragmentManager().findFragmentById(R.id.article_fragment);
if(articleFrag != null && articleFrag.isVisible()) {
articleFrag.updateArticleView(position);
} else {
articleFrag = new ArticleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction().replace(R.id.fragment_container, articleFrag);
transaction.addToBackStack(null);
transaction.commit();
}
}
}
As soon as the activity is started, I check if fragment_container that is the FrameLayout is present or not. If it is not present, then the layout with the 2 fragments has been loaded. Hence no need to add the fragments dynamicaly as they are already present.
Otherwise, I check if the savedInstanceState is null or not. If null, then I create a new HeadlinesFragment and add it to the frame. If it is not null, then it means that the activity has already been created previously, and hence the HeadlinesFragment must have been added already. No need to add it again. So, return.
And onArticleSelected() method replaces the existing fragment in the frame with the ArticleFragment, or if in the other layout, it simply updates the fragment as it is already present. It is called from the HeadlinesFragment when an item is selected.
Now, this all works perfectly well if we enter the activity in portrait mode and then change the orientation. No problem. Flawless.
But if we enter the activity in the landscape mode, as soon as I change the orientation to the portrait mode, a blank screen is shown.
The reason being, the onCreate() is called, and the savedInstanceState returns as not null. Hence, the HeadlinesFragment is not created and added to the frame.
And yes, if I remove that check, then the app works fine, but that will mean that a new HeadlinesFragment is created and added to the frame each time and gets stacked on top of eachother. Which is not at all desirable.
I cannot implement this by just finding out the orientation and applying the appropriate layout. Because, in large-screen devices, even if it is in portrait mode, it is supposed to show both the fragments at once.
I have tried many convoluted logic. But nothing seems to work. Any help is appreciated.
Entering activity in portrait mode
1> List Items are shown.
2> Clicking items replaces the fragment with the ArticleFragment (details).
3> Changing the orientation, shows both side by side. Everything works.
--->
Entering activity in landscape mode
1> Both the list and details are shown. Everything works.
2> But as soon as the orientation changes, you get the blank screen. As Headlines fragment is not created and added.
-->
It would be really helpful if someone could guide me as to how I can solve this problem. And as the actual project is huge, and this logic has already been implemented, a drastic change in logic is not an option anymore as that will mean re writting thousands of lines of code. Thank you. :)
Ok. I got the problem. The problem is we are using Fragments in xml layouts in large devices.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.android.fragments.HeadlinesFragment"
android:id="#+id/headlines_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.android.fragments.ArticleFragment"
android:id="#+id/article_fragment"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
So android is trying to catch the Fragments in savedInstanceState in MainActivity. When screen rotates, system tries to restore the above Fragments even though different layout loads in potrait mode. And so system considers that the article_fragment is also available on the right side and it tries to update it on click on the Headline.
So, What's the solution ?
I have changed a little code in MainActivity and nothing else :-)
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
setContentView(R.layout.news_articles);
// Check whether the activity is using the layout version with
// the fragment_container FrameLayout. If so, we must add the first fragment
if (findViewById(R.id.fragment_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
/*if (savedInstanceState != null) {
return;
}*/
// Create an instance of ExampleFragment
HeadlinesFragment firstFragment = new HeadlinesFragment();
// In case this activity was started with special instructions from an Intent,
// pass the Intent's extras to the fragment as arguments
firstFragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
}
}
So what I have done just I told the system that I don't want any thing restored by using super.onCreate(null); so its restoring nothing now.
About the blank screen you are getting
Whenever you start activity in landscape mode. It loads large display by default. Without entering into if satement. Because it can't get fragment_container in landscape mode. And then you rotate screen to load portrait layout and system gets savedInstanceState != null and it returns without loading HeadlinesFragment. So you get bank screen.
So I have commented If statement as you can notice.
/*if (savedInstanceState != null) {
return;
}*/
So now It load everything correctly.
No issue
Download the code sample from this Developer site and modify it according to your needs.
Build Dynamic UI with fragments
Its super and reliable code.
I'm sure there are reasons why the Fragment was rendered correctly in Portrait -> Landscape and not in the reverse but one thing is very clear about the Activity lifecycle
Called when the activity is starting. This is where most
initialization should go: calling setContentView(int) to inflate the
activity's UI, using findViewById(int) to programmatically interact
with widgets in the UI, calling managedQuery(android.net.Uri,
String[], String, String[], String) to retrieve cursors for data
being displayed, etc.
Note the part that says This is where most initialization should go. By exiting after checking that savedInstanceState is null, you're leaving it up to the super class to restore your Fragment which is not a good idea considering the previous view was destroyed.
My advice, inspect the content of savedInstanceState instead of just checking if it is null. Ensure that it contains enough data to restore your previous state, if not initialize your Fragment.
The best practices with Fragments involve implementing all necessary methods to monitor the state.

custom rows in ListView

i have the following problem, I am writing an app that repeats the http://www.rottentomatoes.com/mobile/ website, if you look on the site there are deviders the "Opening this Week", "Top Box Office" and "Also in Theaters" so my question is, how do i add this custom rows to the ListView that holds all the movies
here is what i have so far, please pitch me some ideas so it will look exactly like on the site
The easiest way to achieve this would be to include the dividers in the layout of item views. Then make them VISIBLE only when the view maps to the first item of a new category, otherwise leave them as GONE.
Example (in semi-pseudocode):
First, make your item layout like this:
<LinearLayout orientation=vertical>
<TextView id=divider>
<your original item layout>
</LinearLayhout>
Then, in the adapter's getView():
Film item = getItem(position);
boolean needsDivider = (position == 0 || getItem(position - 1).filmCategory != film.filmCategory);
if (needsDivider)
{
dividerView.setVisibility(View.VISIBLE);
dividerView.setText(getFilmCategoryName(film.filmCategory));
}
else
dividerView.setVisibility(View.GONE);
A more fancy solution would be to use a library like StickyListHeaders. It doesn't work as your mobile site example, but it's probably even better (e.g. the headers don't scroll up out of the screen).

Categories