I would like to make a curved on-screen keyboard for Android. I have examined the softkeyboard, and various other on screen keyboards on google code. None that I have found involve a keyboard shape that is anything other than a rectangle. Ideally, I would like to create a keyboard that consists of keys distributed across two semi-circles on opposite sides of the screen (i.e., imagine holding a tablet by the sides and being able to hit the keys with your thumbs).
Of the code I have examined, onscreen keyboards are created as views (usually extending KeyboardView) and appear as a continuous bar across the bottom of the screen. As an approximation to my goal, I have tried to alter code I found on google code (dotdash-keyboard-android) to only draw its keys in the lower left-hand corner and leave the lower-right hand corner transparent. I have been able to override onMeasure to affect the dimensions of the view (see below), but this only seems to alter the positions of the keys and not the positions of the container. In other words, there is still a black bar filling the bottom of the screen.
//Located within KeyboardView
#Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
this.setMeasuredDimension(200, 200);
}
Is what I want to do even possible? Is there a more correct term for this? Are there projects I can use as examples?
I have also tried to set the view's dimensions using this.setLayoutParams -- but, these calls seem to have no effect. I have also tried to use this.getParent to access the parent view (if one even exists) and change it's dimensions but this approach does not work (or, I'm just doing it wrong). Any help would be much appreciated.
Thank you
UPDATE: 12/21/2012 - I think I need to override the parent class's onDraw method. Looking here , it looks like KeyboardView's onDraw method draws to a canvas that is equal to the size of the screen using the following code:
final int width = Math.max(1, getWidth());
final int height = Math.max(1, getHeight());
mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBuffer);
I think I can override onDraw and draw whatever I want on the canvas.
UPDATE: 12/21/2012 - I've overridden onDraw and it's now clear that the keyboardView is the dimensions I'm setting it to (200x200). Using Hierarchyview, I can see that the keyboardview is inside a framelayout with the id InputArea. So, the horizontal bar that fills the entire width is this framelayout. But, I'm not creating it -- where does it come from and how can I alter its dimensions?
UPDATE: 12/22/2012 - After more testing, it seems like the behavior (dimensions) of a keyboardview are in-part determined by the activity that calls it. In the browser, I get the behavior I've been describing: the height of the browser window shrinks to accommodate a bar across the bottom of the screen that holds the keyboard, even if the width of the keyboard is less than the width of the screen. In the calendar app, the keyboard size appears as I have set it (as a square in the lower-left hand corner) with the calendar appearing unchanged beneath it. So, it seems impossible to reach my goal with most apps using this approach. An alternative approach might be to have the IME service create a popupwindow or dialog. One problem is that popupwindows need a parent view or anchor to attach to and I don't think it's possible to find the top-most view from the IME service. Perhaps I can create a transparent view over the current activity and place the popup on top of that?
UPDATE: 12/23/2012 - Progress. I've figured out how to display a popup from the keyboard IME. The next step is to figure out how to make the popups a little round/organic. Here's a screenshot of what I accomplished followed by source.
Source. The following method is in the service IME class and called by the child (of the service) view's onMeasure method so that the popups open at the same time the keyboard is drawn. I've set the dimensions of the keyboard to 1x1 so it isn't visible. The log statements are there to help me figure out how to position the popups.
public void initiatePopupWindow()
{
try {
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
//display.getSize(p);
Log.i("dotdashkeyboard","initiatePopupWindow (from IME service)");
LayoutInflater inflater = (LayoutInflater) this.getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layoutLeft = inflater.inflate(R.layout.popup_layout_left,null);
View layoutRight = inflater.inflate(R.layout.popup_layout_right, null);
// create a 300px width and 470px height PopupWindow
int popupHeight = 300;
int popupWidth = 200;
if (popUpLeft == null) popUpLeft = new PopupWindow(layoutLeft, popupWidth, popupHeight, false);
if (popUpRight == null) popUpRight = new PopupWindow(layoutRight, popupWidth, popupHeight, false);
int ypos = 0;
int xposRight = 0;
if (display.getRotation() == Surface.ROTATION_0) {
ypos = -(dm.heightPixels / 2 + popupHeight/2);
xposRight = (dm.widthPixels - popupWidth);
Log.i("dotdashkeyboard","test rotation=normal");
} else if (display.getRotation() == Surface.ROTATION_90) {
ypos = -(dm.heightPixels / 2 + popupHeight/2)/2;
xposRight = (dm.widthPixels - popupWidth)*2-popupWidth;
Log.i("dotdashkeyboard","test rotation=90-degrees");
} else {
Log.i("dotdashkeyboard","test rotation=unknown=" + display.getRotation());
}
popUpLeft.showAtLocation(inputView, Gravity.NO_GRAVITY, 0, ypos);
popUpRight.showAtLocation(inputView, Gravity.NO_GRAVITY, xposRight, ypos);
Log.i("dotdashkeyboard","test created popup at ypos="+ypos + " xposRight=" + xposRight);
Log.i("dotdashkeyboard","test screenWidth=" + dm.widthPixels + " screenHeight=" + dm.heightPixels);
Button cancelButton = (Button) layoutLeft.findViewById(R.id.popup_cancel_button);
//cancelButton.setOnClickListener(inputView.cancel_button_click_listener);
cancelButton.setOnClickListener(cancel_button_click_listener);
} catch (Exception e) {
e.printStackTrace();
}
}
You seem to be on the right track here. As you've noted, most activities will shrink their views to provide space for the keyboard window, so if you want the calling activity to fill the screen you need to use a secondary window, such as a PopupWindow. You can set the dimensions of your main window to 0x0.
Have you tried calling popUpLeft.setBackgroundDrawable(null) / popUpRight.setBackgroundDrawable(null)? This should remove the semi-transparent background and show only whatever you draw on top.
Related
With Instagram when a user clicks on an EditText view the next scenario occurs:
an emoji view is displayed [Not necessary in my case]
story still maintains its width & height [Not being resized] [Required]
keyboard is opened [Required]
seems like view has a transparent background
For my case when I used ADJUST_RESIZE I got the view displayed properly but ImageView of story must have scaleType [fitXY or centerCrop] which has a bad UI with some images, if I did not make it with these scaleTypes ImageView will be resized and have margins beside it.
When not use ScaleTypes[fixtXY, centerCrop] while keyboard is opened:
while keyboard is closed:
The solution is to make ImageView with a fixed size.
private fun makeViewFullWidth(view: View) {
val point = Point()
// point will be populated with screen width and height
activity?.windowManager?.defaultDisplay?.getSize(point)
val param = view.layoutParams
param.width = point.x
param.height = point.y
view.layoutParams = param
}
you can also check this article
I have a fairly complicated situation where I need to either process events in a custom view, which is added via WindowManager, or pass them to the underlying window if it is outside of the wanted area. The wanted area is the containerView where it can be smaller from the root view itself, or may have equal width / height.
The view has a size of 28x28, but it can grow up until 60x60. The growing part is done with ValueAnimator where current width and target width is determined by the ValueAnimator.getAnimatedValue() (in this case, between 28 and 60). The window needs to consume the event if it has been clicked on, or the target view which may be smaller than the window itself, is clicked.
An example of the layout looks like this:
<FrameLayout android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout android:id="#+id/containerView"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center">
<!-- rest of the view, not important -->
<!-- the containerView can have 28x28 size or
60x60 size -->
</FrameLayout>
</FrameLayout>
The animated view is the one that is defined with android:id="#+id/containerView".
I've tried to attach the view using regular layout params, like this, to make the window layout dynamic:
WindowManager manager = context.getSystemService(WindowManager.class);
View rootView = LayoutInflater.from(context).inflate(resId, null, false);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
params.flags = FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH;
manager.addView(rootView, params);
And this similar code block adds the view with 28x28 size, that's not a problem. However, while animating to 60x60 size depending on a state change (on containerView), the animation flickers a lot. I guess it happens because both view itself and the window needs to be re-sized. I've tried to use setLayerType(HARDWARE, null) but that didn't seem to work. Then I've found another workaround, which is increasing the size of the window directly, before starting the animation, by giving it fixed width-height values, like this:
params.width = dpToPx(60);
params.height = dpToPx(60);
manager.updateViewLayout(rootView, params);
And after this, I start the growing animation, which changes the containerView width and height gradually. With this way, animation is smooth, even on lower-end devices so I think it's a good optimization.
The problem begins with the window size change. You see, containerView has to have the attribute android:layout_gravity="center" to position the view to window's center. But, increasing the window width and height changes the view's position. To overcome that, I've decided to write another approach by doing something like this:
// This method is inside the root view, which contains
// the WindowManager.LayoutParams as its layout params.
private void setWindowSize(int widthPx, int heightPx)
{
WindowManager.LayoutParams params = getLayoutParams(); // ignore cast
int oldWidth = params.width;
int oldHeight = params.height;
int differenceWidth = widthPx - oldWidth;
int differenceHeight = heightPx - oldHeight;
// Position the view relatively to the window so
// it should look like its position is not changed
// due to containerView's center layout_gravity.
params.x -= differenceWidth / 2;
params.y -= differenceHeight / 2;
params.width = widthPx;
params.height = heightPx;
// Update itself since this is already the root view.
manager.updateViewLayout(this, params);
}
The code above was causing the position change happening with animation. Hence, I've searched if this animation can be disabled, and found an answer here which seems to be working with Android 10 emulator. However, I don't think this is a reliable approach, as most manufacturers change source codes of framework classes to implement their own themes etc. so I'm looking for a more reliable approach. The change also cause a flicker due to the containerView.onLayout() operation, presumably happening after manager.updateViewLayout() is executed, where it appears on top-left for one frame and on center on the 2nd frame, visible to the eyes.
At this point, I can only think of some ways to prevent these bugs:
1) Process touch events only on certain states (such as the coordinates intercepting the containerView)
2) Make the view non-touchable after receiving MotionEvent.ACTION_OUTSIDE which will indicate a touch event happened outside of the view's boundaries.
1st one has a flaw: If the view is clickable in all cases, it becomes clickable starting from the root view, and once the touch event is received from that view, it is not transferred to other windows (a.k.a underlying applications) which cause an issue.
2nd one seemed a good approach for me, but the event MotionEvent.ACTION_OUTSIDE does not contain any specific x or y coordinates so it is impossible to tell if the event occurred in window's boundaries. If this was possible, I'd add FLAG_NOT_TOUCHABLE to layout params and updated the view, and removed that flag if the touch is to be processed.
So, my question is:
Can a custom view, that has been added with a WindowManager choose to deliver the events further based on, i don't know, returning false from dispatchTouchEvent() or something? Or, is there a way to receive all touch events even outside our application with the specific screen coordinates so I can change the window flags depending on it?
Any help is appreciated, thank you very much.
I was able to resolve the issue by applying an ugly hack. I've used a second window, where the window itself is full screen and contains flags FLAG_NOT_FOCUSABLE and FLAG_NOT_TOUCHABLE and since the touch is disabled the events are passed below window.
The window-resize flickering depending on the animation was the cause, so I've thought about using a temporary view, added the view to a second window, by getting the cache of the view itself using a bitmap and a canvas (the states are cached and recycled by the way), and making the image view visible, setting the view on the original window as INVISIBLE and after making sure it became invisible (by using ViewTreeObserver.addOnDrawListener because the draw function is called) changing window size.
With this approach, the view becomes already invisible while the window size is changed, and translated accordingly, which eliminated the possibility of the buggy view.
Then, after the layout is complete (I've also made sure by using ViewTreeObserver.addOnGlobalLayoutListener() and waiting for the view to be placed on the target coordinates relative to parent), switched the views. Extra memory is used because of the extra added window and image view and bitmap, but the issue seems to be resolved.
The only remaining thing is how to disable window animations with the call windowManager.updateViewLayout() because the flag that the other question mentioned is apparently added in API 18, whereas this app targets to API 16. On the rest of the emulators and the devices that I've tested on seem to have this flag consistently, and the window translate animations seem to be disabled successfully.
I would like to implement a list effect in android as the one displayed in the Ultravisual Iphone app :
The similar effect can be view on the Expo Milano 2015 app in android.
I would like the top item get bigger when sliding down the ListView.
I have no idea how this can be done... Is it an animation on the first item in the current view?
If someone has an example or a clue to achieve this, it will be great!
Thanks
Well I tried to achieve that effect and it looked like this:
First you need to start defining your max and min font size. I did this:
private final int MAX_FONTSIZE=50;
private final int MIN_FONTSIZE=12;
Next you need to save your screen total height. On your onCreate save it like this:
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
mScreenHeight = size.y;
Then override your listview onScroll event and do something like this:
#Override
public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {
if(listview.getChildCount()>0)
for(int i=0;i<listview.getChildCount();i++)
{
float font = Math.min(Math.max(MAX_FONTSIZE - (float)(((MAX_FONTSIZE-MIN_FONTSIZE)/(mScreenHeight*0.3)))*Math.max(listview.getChildAt(i).getTop(),0),MIN_FONTSIZE),MAX_FONTSIZE);
((TextView)listview.getChildAt(i)).setTextSize(font);
}
}
The 0.3 means that at about 30% of your screen it will always be the minimum font size. You can tweak that value for whatever you want. Remember that listview.getChildAt(i) will return the view that you inflate on your adapter. In my case it's just a simple textview, that's why it's safe to cast it to TextView. You might need to call findViewById to get your textview.
You also might need to make the TextView centered so it looks prettier.
EDIT:
Since op want to change the view's size (which should not be used with this code) here is some hack you can do. Start by changing your min/max constants to what you want (100 & 250). Then proceed to create a flag that controls if the listview is scrolling or not. On your onScrollStateChanged add this line isScrolling= i == SCROLL_STATE_TOUCH_SCROLL || i == SCROLL_STATE_FLING; where i is the second parameter of the onScrollStateChanged. Next change the line to if(listview.getChildCount()>0 && isScrolling). The next step is to change the view height. To do this you have to change it's layoutParams.
listview.getChildAt(i).setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Math.round(font * getResources().getDisplayMetrics().density)));
Remember this is, like I said, a simple hack. This is not, by far, the best solution to this. But since it's a complex thing to do let's stick with the basics.
I'm currently extending the SlidingDrawer class and want to resize the width of the content and can't quite get it to work right. Right now I'm setting the width of the whole view (handle and content) and it works for resizing purposes but also introduced a visual glitch when I move the handle it jumps to the new size for a split second and then returns to the handles position. I'm thinking that the problem is stemming from the onMeasure() or onLayout() calls that are happening in the base SlidingDrawer that are preventing the content area to be resized but am not completely sure.
I'm using getLayoutParams().width = newWidth; to resize the whole view but would like to use something like mContent.getLayoutParams().width = newWidth;.
The source code for the onMeasure() is here and for the onLayout() here.
Any insight into why the content area can't be resized would be great. Thanks!
So I finally figured it out if anyone else was having an issue with this. Basically when you want to resize the layout you need to measure() the layouts after the size change. Without the offsetLeftAndRight() call the handle will "jump" to the new size for a split second so setting the offset eliminates that "jump".
A simplified version of what I did was essentially:
public void resize() {
int previousPosition = mHandle.getLeft();
//Set the new size of the content area
mContent.getLayoutParams().width = width;
//Measure the newly sized content area and adjust the layout
mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getBottom() - getTop(), MeasureSpec.EXACTLY));
mContent.layout(handleWidth + mTopOffset, 0, mTopOffset + handleWidth + content.getMeasuredWidth(), content.getMeasuredHeight());
/* Remeasure any other views that were resized also here */
//Not required but helps position the handle correctly
mHandle.offsetLeftAndRight(previousPosition);
}
OK, so I am starting to get a hang of building Android apps, well at least as much a programmer can after a few days - I am proud of what I have learned so far.
Anyways, I want to force login on the main activity - this I am doing by fetching a SharedPrefernece and than checking if that piece of information is null and than getting a PopupWindow which holds the "login" fields and options.
This PopupWindow has a Flipper inside, which is fine and I got working fine when the certain options are choosen.
I am having problems displaying this PopupWindow to just be the size of the content (wrap_content) as when I set the PopupWindow.setAtLocation()
Now, here is what I have been trying to do to get the size of the popup - as mentioned a few times on here:
popup.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
final PopupWindow pw = new PopupWindow(popup,popup.getMeasuredHeight(),popup.getMeasuredWidth(), true);
Note that popup is the inflator of the actual layout of the Popup, pw is the actual PopupWindow object.
Now, I want to get the actual size of just the popup window (so that way it isn't streched out over the page, but rather just in the center looking like a normal popup should.
Also, with the ViewFlipper. I want it to update the size of the popup when it switch pages (so the page should be sizing up and down per page) is there a way to make this work as well? I tried pw.update() but that didn't work out very well.
You want to measure the actual layout you will be adding to your PopupWindow. I just solved a similar problem of putting a ListView inside a PopupWindow. The trick is to override onMeasure() in your View. Remeasure that ViewFlipper everytime it changes.
#Override
public void onMeasure(int x, int y)
{
super.onMeasure(x, y);
if(x == View.MeasureSpec.UNSPECIFIED && y == View.MeasureSpec.UNSPECIFIED)
{
//measure your child views here
//tell the view it has been measured
this.setMeasuredDimension(width, height);
}
}