I was wondering what the best way to approach launching activities from other views in a modular way. I'm trying to figure out a way to tell my "button" which activity to fire off once its been selected in the 'onTouchEvent'. Currently, I have a main activity that creates and sets my view to my 'MainMenu'. My main menu defines a MenuItem class that defines a rect for drawing a button, and firing off an activity when intersected/touched/clicked. However, I'm having some difficulty firing off the activity. Below are just a few snippets of code demonstrating some of what I'm trying to achieve:
public class MainMenu extends View {
...
private Vector<MenuItem> menuItems;
private MenuItem testButton;
private MenuItem testButton2;
public MainMenu(Context context) {
...
// Create our menu buttons and load their specific images
testButton = new MenuItem(context, new OptionsMenu(), 150, 50, imgButtons, 256, 64, 0, 0);
testButton2 = new MenuItem(context, OptionsMenu.class, 150, 200, imgButtons, 256, 64, 0, 0);
// Store our buttons
menuItems = new Vector<MenuItem>(5, 2);
menuItems.add(testButton);
menuItems.add(testButton2);
}
...
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN)
super.onTouchEvent(event);
// Create our menu item iterator
Iterator<MenuItem> menuItemsIter = menuItems.iterator();
Object element;
// Loop through our menu items, drawing them
while(menuItemsIter.hasNext()) {
element = menuItemsIter.next();
if(((MenuItem)element).HasIntersected((int)event.getX(), (int)event.getY())) {
((MenuItem)element).LaunchActivity();
}
}
return true;
}
}
class MenuItem {
...
private Context container = null; // Indicates which activity contains us
private Object startObject = null; // Which activity we'll start/execute
public MenuItem(Context context, Object object, int xPos, int yPos, Bitmap image,
int imageWidth, int imageHeight, int xOffset, int yOffset) {
...
container = context;
startObject = object;
}
...
public void LaunchActivity() {
if(startObject != null) {
Intent activity = new Intent(container, startObject.getClass());
container.startActivity(activity);
}
}
}
I tried setting my MenuItem's Object two different ways (new OptionsMenu() and OptionsMenu.class), but neither seem to work. I tried dodging the use of the MenuItem's startObject when created a new Intent, and using (container, optionsMenu.class) for parameters instead. Which didn't work either. From what I know this is the correct way to fire off an activity, but I'm guess I'm missing a step somewhere.
Also, I read a few articles/posts of people mentioning the use of callbacks, but on the Activity side instead of the View side. However, it wasn't very clear if there were built in Android callbacks I should use, or if I should create my own callbacks and setup my own system.
Any information about what I'm doing wrong, or what I could do differently to approach this differently/better would be appreciated. Thanks.
IMHO, just a Button supports sending click events to an OnClickListener, your custom View should have its own custom event interface for sending its own custom events to the controller (e.g., an activity). It is up to the controller to arrange to do something with those events, such as starting up other activities.
Related
I'd like to use the swipe(onFling) feature of android gestures. I have some adjacent pictures to
chancge into an other picture, in case of swiping.(Just like i demonstrated on the picture)
It should work regardless, which direction the player swipe his/her finger.
Could you give me any link? Or any idea which components should i use?
Since your gesture appears to apply the premise that it must:
Gesture must include all adjacent views.Gesture has a direct linear begginning and endGesture is a single movementGesture does not conflict with other similar gestures
You might want to read on "MotionEvent", and the onTouch listener for views.
A single flag private static View beganOn; on the parent class (I am supposing an Activity). Then:
public void onTouch(View v, MotionEvent m){
if(beganOn!=null){
begaOn = v;
return;
} else {
// Where the view Tag, is an Integer to state what number it is in the sequence.
doSelectionOfViews(beganOn.getTag(),v.getTag());
begaOn = null;
}
}
override the onfling() method of the Gesture Detector. You will be able to get the Swipe direction Under this. Now take two counters for both direction and increment it(i.e count++) in the Right/left swipe and vice versa. Below I am posting the code by which you will be able to create that circular indicator. Whichever you want to make highlighted, You need to pass the index only.
public void updateIndicator(int currentPage) {
image_indicator.removeAllViews();
DotsScrollBar.createDotScrollBar(this, image_indicator, currentPage, 5);
}
Here image_indicator is an linear layout defined in xml.
public static class DotsScrollBar
{
LinearLayout main_image_holder;
public static void createDotScrollBar(Context context,
LinearLayout main_holder, int selectedPage, int count)
{
for (int i = 0; i < count; i++) {
ImageView dot = null;
dot = new ImageView(context);
LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
vp.setMargins(8, 8, 8, 8);
dot.setLayoutParams(vp);
if (i == selectedPage) {
try
{
dot.setImageResource(R.drawable.page_hint_pre);
}
catch (Exception e)
{
}
} else
{
dot.setImageResource(R.drawable.page_hint_def);
}
main_holder.addView(dot);
}
main_holder.invalidate();
}
}
Pass the index in the upDateIndicator() method to make that particular incator highlighted.
My app supports API level 10 (Gingerbread) upwards. One of the activities draws a chart which works perfectly on later versions, but when running a level 10 emulator I get an extra call to View.onDraw with the wrong canvas ID and this causes the screen to go blank. (It's not just the emulator - the problem was reported by someone running on their Gingerbread phone.)
Normal operation is for onDraw to be called twice - the first time from the framework, where I take the canvas ID from, and the second time from my call to invalidate(), which passes the same canvas ID. These two calls happen with the level 10 emulator, but then there is a third call with a different canvas ID - i.e. not belonging to the view, and this blanks it out.
The activity is derived from SherlockActivity to provide an action bar, and I believe this is what is causing the problem.
Relevant code from my activity class:
public class Chart extends SherlockActivity implements OnGestureListener, OnDoubleTapListener, OnScaleGestureListener
{
public static boolean mDataSet = false;
private ChartView mView;
private Menu mMenu;
private GestureDetector mDetector;
private ScaleGestureDetector mScaleDetector;
private ActionBar mActionBar;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mDataSet = false;
mActionBar = getSupportActionBar();
mActionBar.setHomeButtonEnabled(true);
mActionBar.setDisplayHomeAsUpEnabled(true);
mActionBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
mView = new ChartView(this, mCentreLat, mCentreLong, mRadius);
setContentView(mView);
setTitle("");
Context context = getApplicationContext();
mDetector = new GestureDetector(context, this);
mScaleDetector = new ScaleGestureDetector(context, this);
}
#Override
public void onConfigurationChanged(Configuration config)
{
super.onConfigurationChanged(config);
mDataSet = false;
}
// Menu handling
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
mMenu = menu;
MenuInflater inflater = getSupportMenuInflater();
inflater.inflate(R.menu.chart_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
boolean handled = true;
switch (item.getItemId())
{
case android.R.id.home:
finish();
break;
case R.id.viewOptions:
mDataSet = false;
Intent i = new Intent(getBaseContext(), ChartSettings.class);
startActivity(i);
break;
// Other menu options here...
default:
handled = false;
}
return handled;
}
Relevant code from my View class:
public class ChartView extends View
{
public ChartView(Context context, float centreLat, float centreLong, float radius)
{
super(context);
mContext = context;
mCentreLat = centreLat;
mCentreLong = centreLong;
mRadius = radius;
}
#Override
protected void onDraw(Canvas canvas)
{
// First time
// (We pick up the canvas id mCanvas from the parameter.)
// (Nothing much else relevant here, except this is where stuff
// gets initialized, then in setDataInfo(), mDataSet is set true
// and invalidate() is called at the end.)
if (!Chart.mDataSet)
{
setBackgroundColor(Color.WHITE);
mCanvas = canvas;
initPaint();
setDataInfo(); // invalidate() called at end
}
else
{
// Second call to onDraw() with same canvas id comes here,
// then an unwanted third call with a different canvas id, only
// with the Level 10 (Gingerbread) emulator.
// This is where the various lines and shapes are plotted
// on the canvas (mCanvas)
plotLinesAndShapes();
}
Please can anyone explain why the third call happens with the Gingerbread emulator (or phone)? The effect is that the screen is blanked (completely white).
By the time it gets to this function it's too late and the call cannot just be ignored - the screen goes blank as the call stack unwinds.
There is a workround - if the user chooses view options from the menu, then immediately returns to the chart, it is redrawn and behaves normally from then on.
The problem is that you are keeping a reference to the Canvas you receive as a parameter. There is no guarantee whatsoever that this Canvas instance will be valid after the current frame is done. You could for instance receive a different Canvas instance on every frame. You will typically receive a different Canvas when the View is rendered into a Bitmap (View.setDrawingCacheEnabled() for instance.)
Instead of keeping a reference in mCanvas, just pass the canvas you receive to plotLinesAndShapes().
I don't understand why invalidate(Rect) is invalidating the entire region.
The region is divided up into 5 sections and a line graph is being drawn in each one. Each section contains 100 points. As the data arrives for times tn to tn+100 I call invalidate(new Rect(left, top, right bottom)) where top is the top of the screen in height (but a lower numerical value than bottom). This invokes a call to the onDraw() method. The region from tn to tn+100 is drawn, but the previously drawn segment in region tn-100 to tn is erased. It continues that way forever. Each invalidate draws in only that region (since that is the only data I have) which IS correct, but all the previously drawn data is erased. So I get a marching segment!
In other words, I get identical behavior if I call invalidate() or invalidate(Rect).
I am assuming that the parameters of the Rect() are pixels and are getting the values based upon the height and width of the AlertDialog window in which this is being drawn.
The hope is eventually to reduce the region of 'Rect()' so I can simulate real time drawing and only invalidate time step t to t+1 instead of a region.
I must be doing something stupid.
I hope that the fact it is being done in an AlertDialog is not the issue.
This part is for trying to help 'android developer' help a noob like me get this right.
First the sequence of events:
1. Data is received via Bluetooth in a callback
2. If it is the right type of data, a BroadcastReceiver in the main activity (UI thread) is signaled and from there a routine is called that sets the parameters of a WaveFormView extends View class and then ShowDialog(id) is called which calls the onCreateDialog(id) callback in the main activity.
3. Then I call invalidate().
4. The dialog pops up and then the graph is drawn.
5. All subsequent calls to ShowDialog(id) bypass the onCreateDialog(id)
That works but the entire region is always invalidated regardless of the parameters. There are also no user events here. From your example the best I could come up with is the following where I place invalidate in the onShow() instead of calling myself after the showDialog(id)
#Override
protected Dialog onCreateDialog(int id)
{
Log.d(TAG, "Alert Dialog 'onCreateDialog' method has been called with id " + id);
Builder bldr = new AlertDialog.Builder(this);
AlertDialog alert = bldr.setView(waveForm).setNegativeButton("Dismiss " + id,
new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int id)
{
dialog.cancel();
}
}).create();
// I tried adding this and invalidating here worked only first pass
alert.setOnShowListener(
new DialogInterface.OnShowListener()
{
#Override
public void onShow(DialogInterface dialog)
{
// call to invalidate
waveForm.drawArea();
}
});
//alert.getWindow().setLayout(alert.getWindow().getAttributes().width, alert.getWindow().getAttributes().height / 2);
alert.getWindow().setLayout(waveForm.getCurrentWidth(), waveForm.getCurrentHeight());
return alert;
}
However the onShow() does not get called.
The method in the main activity that calls the showDialog is
private void displayRtsa(int[] rtsaReceived)
{
// rtsaReceived[0] has the agentsink hash code
int agent = rtsaReceived[0];
// rtsaReceived[1] has the index of the RtsaData object updated
int index = rtsaReceived[1];
TreeMap<Integer, RtsaData[]> agentRtsa = BluetoothPanService.getAgentRtsaMap();
RtsaData[] rtsaDataValues = agentRtsa.get(agent);
int dialogId = 0;
synchronized(dialogIds)
{
int i = 0;
if(dialogIds.containsKey(agent + index) == false)
{
for(i = 0; i < dialogIds.size(); i++)
{
if(dialogIds.containsValue(i) == false)
{
break;
}
}
dialogIds.put(agent + index, i);
}
dialogId = dialogIds.get(agent + index);
}
final int id = dialogId;
Log.d(TAG, "Dialog id being shown = " + dialogId);
waveForm.setPeriod(rtsaDataValues[index].getPeriod());
waveForm.setMaxMin(rtsaDataValues[index].getMinValue(), rtsaDataValues[index].getMaxValue());
waveForm.setColor(Color.argb(255, 255, 200, 0));
waveForm.setData(rtsaDataValues[index].getData());
waveForm.setTitle(rtsaDataValues[index].getType());
showDialog(id);
// invalidate
// waveForm.drawArea(); (try to do in onCreateDialog callback)
}
This is probably a completely wrong approach. Probably openGl is the only way.
By the way, thanks for putting up with me!
i think it depends on what exactly you do the invalidation on . if the view you are calling the invalidation on didn't handle a rectangular invalidation , the default invalidation takes place.
in any case , if you wish to change the behavior , you can change it yourself. for the "onDraw" method , use the next code in order to fetch the invalidated rectangle :
public class InvalidateTestActivity extends Activity
{
static class CustomView extends ImageView
{
public CustomView(Context context)
{
super(context);
}
#Override
protected void onDraw(Canvas canvas)
{
final Rect r = canvas.getClipBounds();
Log.d("DEBUG", "rectangle of invalidation:" + r);
super.onDraw(canvas);
}
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
CustomView customView = new CustomView(this);
customView.setLayoutParams(new FrameLayout.LayoutParams(200, 200));
customView.setBackgroundColor(0xffff0000);
customView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v)
{
v.invalidate(new Rect(0, 0, 49, 49));
}
});
setContentView(customView);
}
}
If you have hardware acceleration enabled it looks like it just invalidates the whole view...
ViewGroup:
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}
I have listview that stores the communication history of a person. I have one header inside a listview that acts as a message editor with a edit text and a send button.
When a user types something and press send button the messages adds to the communication list and editor gets empty.
What I want is when user press the send button, the editor should become invisible and Item should be added to the listview. After that the editor should come gradually from the top giving the feel that its moving the items below.
I have implemented a translate animation on the header but what it does is it makes the space for it by pushing the items down and then gradually fills the space which I dont want.
I used the negative margin trick which is explained in this question but It didn't work for me. As we cant use layout params other that AbsListView.LayoutParam for the headers. I tried setting Other params but while animating It gives me ClassCastException. I tracked the exception and its due to code written inside ListView they are trying to cast these params with AbsListView.LayoutParams inside clearRecycledState() method.
Or Is there a way to apply layout params that supports margin on a listview-header.
the code
public class PageListView extends ListView {
private Application app;
private CommListAdapter listAdapter;
private MessageEditorHeader messageEditorHeader;
private MessageItemLongClick mInterface;
private Handler handler;
public ProfilePageListView(Application app, MessageItemLongClick mInterface) {
super(app);
this.app = app;
this.mInterface = mInterface;
this.handler = new Handler();
setupView();
}
public void applyData(ProfileData data){
listAdapter.applyData(data.getUser());
// some other business logic
}
private void setupView() {
messageEditorHeader = new MessageEditorHeader(app);
addHeaderView(messageEditorHeader);
listAdapter = new CommListAdapter(app, mInterface);
setAdapter(listAdapter);
setDivider(null);
setScrollingCacheEnabled(false);
tAnimation = new TranslateAnimation(0.0f, 0.0f, -90.0f, 0.0f);
tAnimation.setZAdjustment(-1);
tAnimation.setDuration(1500);
}
// this gets called whenever the communication gets added to the listview.
public void onNewCommunication(Communication lc) {
listAdapter.onNewCommunication();
if(lc != null && lc.isOutgoing() && !lc.getType().isCall()){
getMessageEditor().startNewMessage();
messageEditorHeader.setVisibility(VISIBLE); // this is overriden method here I m toggling the height 1px and WRAP_CONTENT
messageEditorHeader.startAnimation(tAnimation);
}
}
// few more methods are there.
}
heres the code of message editor
public class MessageEditorHeader extends RelativeLayout {
private MessageEditor msgEditor;
public MessageEditorHeader(AppteraApplication context) {
super(context);
msgEditor = new MessageEditor(context); // Its a relative layout containing edit text and the send button
addView(msgEditor);
}
public MessageEditor getMsgEditor() {
return msgEditor;
}
public void setProgress(int progress){
msgEditor.setProgress(progress);
}
#Override
public void setVisibility(int visibility) {
this.visibility = visibility;
if (visibility == View.VISIBLE) {
ListView.LayoutParams params = new ListView.LayoutParams(ListView.LayoutParams.FILL_PARENT, ListView.LayoutParams.WRAP_CONTENT);
setLayoutParams(params);
}
else {
ListView.LayoutParams params = new ListView.LayoutParams(ListView.LayoutParams.FILL_PARENT, 1);
setLayoutParams(params);
}
}
}
Have you thought about a different approach instead? Maybe you can just put the editor view at the top of the list, but outside the screen, and then use smoothScrollToPosition to transition in. So in reality you're just scrolling the list, but the effect could be what you're looking for.
I love the API Demo examples from the Android webpage and used the AnimateDrawable.java to
get started with a WaterfallView with several straight falling images which works great. Now I like the images to stop when they are clicked. I found out that Drawables can't handle events so I changed AnimateDrawable and ProxyDrawable to be extended from View instead and added a Click-Event-Listener and Handler on the parent WaterfallView. The animation still works great, but the handler doesn't, probably because in AnimateDrawable the whole canvas is shifted when the drawabled are animated. How can I change that example so that I can implement an event handler? Or is there a way to find out where exactly my AnimateDrawables are in the view?
So the more general question is: How to add an Event Listener / Handler to an animated View?
Here are my changes to the example above:
AnimateView and ProxyView instead of AnimateDrawable and ProxyDrawable
ProxyView extended from View and all super calls changed to mProxy
I commented out mutate()
The context is still the main Activity which is passed down in the constructors
In the constructors of AnimateView setClickable(true) and setFocusable(true) are called
And here is the important source code of the parent/main WaterfallView:
public class WaterfallView extends View implements OnClickListener {
private Context mContext;
// PictureEntry is just a value object to manage the pictures
private Vector<PictureEntry> pictures = new Vector<PictureEntry>();
public WaterfallView(Context context) {
super(context);
mContext = context;
pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_0)));
pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_1)));
pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_2)));
pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_3)));
pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_4)));
pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_5)));
pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_6)));
pictures.add(new PictureEntry(context.getResources().getDrawable(R.drawable.sample_7)));
}
#Override
protected void onDraw(Canvas canvas) {
if(!setup) {
for(PictureEntry pic : pictures) pic.setAnimation(createAnimation(pic));
setup = true;
}
canvas.drawColor(Color.BLACK);
for(PictureEntry pic : pictures) pic.getAnimateView().draw(canvas);
invalidate();
}
private Animation createAnimation(PictureEntry picture) {
Drawable dr = picture.getDrawable();
dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
Animation an = new TranslateAnimation(0, 0, -1*dr.getIntrinsicHeight(), this.getHeight());
an.setRepeatCount(-1);
an.initialize(10, 10, 10, 10);
AnimateView av = new AnimateView(mContext, dr, an);
av.setOnClickListener(this);
picture.setAnimateView(av);
an.startNow();
return an;
}
#Override
public void onClick(View v) {
Log.i("MyLog", "clicked "+v);
}
}
Are you going to be clicking widgets(buttons, checkboxes, etc)? Or do you want to be able to click anywhere? I think you want the latter. So in that case you'll need this method:
#Override
public boolean onTouchEvent(MotionEvent event) {
// do stuff with your event
}
This method is ONLY called when the event is NOT handled by a view, so I think you may have to remove some of your onClickListener stuff. Refer to here for more info. And as always, experiment.