I'm trying to add multiple gridviews to a scrollview in linear layout at runtime.
But only first row displays.
Pls help
You can't nest scrollable Views in Android - i.e. ListView, GridView, ScrollView.
You could give a look at the following code:
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.GridView;
public class ScrollableGridView extends GridView {
boolean expanded = true;
public ScrollableGridView(Context context)
{
super(context);
}
public ScrollableGridView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ScrollableGridView(Context context, AttributeSet attrs,
int defStyle)
{
super(context, attrs, defStyle);
}
public boolean isExpanded()
{
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// But do not use the highest 2 bits of this integer; those are
// reserved for the MeasureSpec mode.
int expandSpec = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
else
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
It is a little better version of GridView allowing it to almost work when nested in ScrollView. I said "almost work" since I found it sometimes being 20-30 pixels too short or too long.
You should note that this would prevent the views from being reuse, so it is way heavier than normal GridView.
In my case I ended up extending LinearLayout and using it to align its children in columns. It wasn't really hard - I can give you examples if you want. :)
I got the GridView example from this answer.
Here is an example for a GridView based on LinearLayout:
import java.util.List;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
public class GridLikeLayout extends LinearLayout {
private static final int DEFAULT_ITEMS_PER_ROW = 1;
private final int DEFAULT_COLUMN_WIDTH = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 150, getContext().getResources()
.getDisplayMetrics());
private int itemsPerRow = DEFAULT_ITEMS_PER_ROW;
private List<View> innerViews = null;
private int columnWidth;
public GridLikeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.GridLikeLayout);
// itemsPerRow = a.getInt( R.styleable.GridLikeLayout_columns,
// DEFAULT_ITEMS_PER_ROW);
try {
columnWidth = (int) a.getDimension(
R.styleable.GridLikeLayout_column_width, DEFAULT_COLUMN_WIDTH);
} catch (UnsupportedOperationException uoe) {
columnWidth = (int) a.getInt(
R.styleable.GridLikeLayout_column_width, DEFAULT_COLUMN_WIDTH);
}
setOrientation(LinearLayout.VERTICAL);
}
public GridLikeLayout(Context context) {
super(context);
setOrientation(LinearLayout.VERTICAL);
}
public void setInnerViews(List<View> innerViews) {
this.innerViews = innerViews;
processViews();
}
public List<View> getInnerViews() {
return innerViews;
}
protected void processViews() {
if (null != innerViews) {
LinearLayout innerContainer = null;
innerContainer = generateInnerContainer();
int childrenCount = innerViews.size();
for (int index = 0; index < childrenCount; ++index) {
if (isFull(innerContainer)) {
addInnerContainer(innerContainer);
innerContainer = generateInnerContainer();
}
View child = innerViews.get(index);
if (null != child.getParent()) {
((ViewGroup) child.getParent()).removeView(child);
}
addInnerView(innerContainer, child);
}
addInnerContainer(innerContainer);
}
}
protected boolean isFull(LinearLayout innerContainer) {
return 0 == (innerContainer.getChildCount() % itemsPerRow)
&& 0 < innerContainer.getChildCount();
}
protected void addInnerView(LinearLayout innerContainer, View child) {
int width = LayoutParams.WRAP_CONTENT;
int height = LayoutParams.WRAP_CONTENT;
LayoutParams innerParams = new LayoutParams(width, height);
innerParams.weight = 1;
innerParams.gravity = Gravity.CENTER;
innerContainer.addView(child, innerParams);
}
protected void addInnerContainer(LinearLayout innerContainer) {
LayoutParams params = generateDefaultLayoutParams();
params.width = LayoutParams.MATCH_PARENT;
addView(innerContainer, params);
}
protected LinearLayout generateInnerContainer() {
LinearLayout innerContainer;
innerContainer = new LinearLayout(getContext());
innerContainer.setGravity(Gravity.CENTER);
return innerContainer;
}
public void setOnInnerViewClickListener(OnClickListener listener) {
for (View innerView : innerViews) {
innerView.setOnClickListener(listener);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED) {
if (columnWidth > 0) {
widthSize = columnWidth + getPaddingLeft() + getPaddingRight();
} else {
widthSize = getPaddingLeft() + getPaddingRight();
}
widthSize += getVerticalScrollbarWidth();
}
int childWidth = widthSize - getPaddingLeft() - getPaddingRight();
int columnsNumber = determineColumns(childWidth);
if (columnsNumber > 0 && columnsNumber != itemsPerRow) {
itemsPerRow = columnsNumber;
removeAllViews();
processViews();
}
}
protected int determineColumns(int availableSpace) {
int columnsNumber = itemsPerRow;
if (0 < columnWidth) {
columnsNumber = availableSpace / columnWidth;
}
return columnsNumber;
}
}
Here is my resource file for the custom attributes:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="column_width" format="dimension|integer">
<enum name="single_column" value="-1" />
</attr>
<declare-styleable name="GridLikeLayout">
<attr name="column_width" />
</declare-styleable>
</resources>
Here is an example usage:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/layoutContainer"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ScrollView
android:id="#+id/itemsScroller"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:id="#+id/itemsLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<your_package.view.GridLikeLayout
xmlns:my="YOUR APPLICATION PACKAGE"
android:id="#+id/MyGrid"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
my:column_width="260dp"
android:focusable="true"
android:gravity="center_horizontal"
android:padding="5dp" >
</your_package.GridLikeLayout>
<View
android:id="#+id/viewSeparator"
android:layout_width="fill_parent"
android:layout_height="2dp" />
<your_package.GridLikeLayout
xmlns:my="YOUR APPLICATION PACKAGE"
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
my:column_width="single_column" >
</your_package.GridLikeLayout>
</LinearLayout>
</ScrollView>
</FrameLayout>
Please tell me if you have any troubles running the example - I'm not able to try it at the moment. So if you have issues, I'll check it up later. Don't forget to change the package names - "your_package" should be the the package where you store the GridLikeLayout and "YOUR APPLICATION PACKAGE" is your app's package - the one specified in the application manifest. :)
Until you put here the layout I could suppose only that you have set wrong horizontal/vertical parameter in your linear layout.
Your first solution does work but does not take into account that GridViews often imply image caching such as in my case. Here your first solution leads to OutOfMemoryError every time I start to scroll.
Has anybody already got an example solution which does take into account image caching?
Related
I am using camera2 API for my project. The preview is set on Google's recommended AutofitTextureView.
But using their code, the preview was stretched when the textureview filled the whole screen. So I found a stackoverflow answer and edited the code to this:
AutoFitTextureView.java:
package com.example.android.camera2basic;
import android.content.Context;
import android.util.AttributeSet;
import android.view.TextureView;
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
#This is the line that I have changed:
if (width > height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
Using the above code and this code:
private static final int MAX_PREVIEW_WIDTH = 1920;
private static final int MAX_PREVIEW_HEIGHT = 1080;
private Size previewSize;
int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
int mSensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
boolean swappedDimensions = false;
switch (displayRotation) {
case Surface.ROTATION_0:
case Surface.ROTATION_180:
if (mSensorOrientation == 90 || mSensorOrientation == 270) {
swappedDimensions = true;
}
break;
case Surface.ROTATION_90:
case Surface.ROTATION_270:
if (mSensorOrientation == 0 || mSensorOrientation == 180) {
swappedDimensions = true;
}
break;
default:
//Log.e(TAG, "Display rotation is invalid: " + displayRotation);
}
Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getSize(displaySize);
int rotatedPreviewWidth = width;
int rotatedPreviewHeight = height;
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
if (swappedDimensions) {
rotatedPreviewWidth = height;
rotatedPreviewHeight = width;
maxPreviewWidth = displaySize.y;
maxPreviewHeight = displaySize.x;
}
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
maxPreviewWidth = MAX_PREVIEW_WIDTH;
}
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
}
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(SurfaceTexture.class)), new PrismaCamera.CompareSizesByArea());
previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, maxPreviewHeight, largest);
public static Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
// Collect the supported resolutions that are smaller than the preview Surface
List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else if (notBigEnough.size() > 0) {
return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[0];
}
}
I was able to get the camera preview to full screen, without stretching the preview. But the problem is that when the width and height of the TextureView is set to a value other than match_parent, the camera preview still fills the whole screen.
For example, This:
<com.camera.AutoFitTextureView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/textureView"
/>
and this:
<com.camera.AutoFitTextureView
android:layout_width="500dp"
android:layout_height="500dp"
android:id="#+id/textureView"
/>
sets the preview to full screen. What I want is that the camera preview should fit perfectly to the width and height of the textureview, without taking the whole space and filling the screen. How to achieve this? The camera preview should be perfect in 9:16, 1:1, 3:4 and all the other ratios. Is this possible? Waiting for your answer. Regards.
Update:
Here is my current layout file:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#android:color/black">
<TextureView
android:layout_width="0dp"
android:layout_height="0dp"
android:id="#+id/textureView"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
android:layout_marginTop="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
I need to introduce data in an EditText but i want to use an virtual keyboard, not the android keyboard. If I use setKeyListener(null) the cursor is invisible even after using setCursorVisible(true).
Is it possible to make an EditText where even if it isn't editable the cursor is visible ?
EDIT 2 :
I found an partial method to do that, but it's not working when i'm double taping the EditText.
I made an setOnClickListner() and an setOnLongClickListner() method for the EditText. In this methods I hide the Soft Input from the Window, also i use setTextIsSelectable(false). My only problem is that when I double tap the EditText the soft input keyboard shows and I dont know how to hide it, I tried to use android:windowSoftInputMode="stateAlwaysHidden" in manifest, but it doesn't work either.
EDIT :
Here is the code that I'm using at this moment for my base converter calculator.
public class MainActivity extends AppCompatActivity {
EditText number;
EditText base;
boolean baseB = false;
String numberS = "0";
String baseS = "10";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(activity_main);
//make the EditText for number and base not editable
number = (EditText) findViewById(R.id.number);
number.setKeyListener(null);
base = (EditText) findViewById(R.id.base);
base.setKeyListener(null);
//... more code here (changing fonts for each EditText and changing status bar color
}
// I have a function for each button all are the same
public void onClickBaseChange(View v) {
if (baseB) {
baseB = false;
// i use toasts at this moment to know when i'm on number or base field
Toast.makeText(this, "Number", Toast.LENGTH_SHORT).show();
} else {
baseB = true;
Toast.makeText(this, "Base", Toast.LENGTH_SHORT).show();
}
}
public void onClickB0(View v) {
if (numberS.length() > 0 && !numberS.equals("0") && !baseB) {
numberS += "0";
number = (EditText) findViewById(R.id.number);
number.setText(numberS, TextView.BufferType.EDITABLE);
number.setSelection(numberS.length());
} else {
if (Integer.valueOf(baseS) >= 1) {
baseS += "0";
base = (EditText) findViewById(R.id.base);
base.setText(baseS, TextView.BufferType.EDITABLE);
}
}
}
public void onClickB1(View v) {
if (numberS.equals("0")) {
numberS = "1";
} else {
numberS += "1";
}
number = (EditText) findViewById(R.id.number);
number.setText(numberS, TextView.BufferType.EDITABLE);
number.requestFocus();
number.setSelection(numberS.length());
}
And the xml looks like this :
<android.widget.RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#color/colorBackground"
tools:context="manastur.calculator.MainActivity">
<EditText
android:id="#+id/base"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="20dp"
android:layout_marginTop="120dp"
android:background="#android:color/transparent"
android:cursorVisible="true"
android:text=""
android:textColor="#color/text"
android:textSize="30dp" />
<EditText
android:id="#+id/number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="30dp"
android:layout_marginTop="50dp"
android:background="#android:color/transparent"
android:cursorVisible="true"
android:text=""
android:textColor="#color/text"
android:textSize="50dp" />
<LinearLayout
android:id="#+id/secondRow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#+id/firstRow"
android:layout_centerHorizontal="true">
<Button
android:id="#+id/b1"
android:layout_width="85dp"
android:layout_height="85dp"
android:background="#drawable/b1"
android:onClick="onClickB1" />
<Button
android:id="#+id/b2"
android:layout_width="85dp"
android:layout_height="85dp"
android:background="#drawable/b2"
android:onClick="onClickB2" />
<!-- from this point on is the same, there are 5 LinearLayouts which
represents the 5 rows of button of the num pad -->
Use this code to achieve that,
While develop I took reference from native Dialpad code
KeypadlessKeypad.java
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.MotionEventCompat;
import android.text.InputType;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class KeypadlessKeypad extends EditText {
private static final Method mShowSoftInputOnFocus = getSetShowSoftInputOnFocusMethod(
EditText.class, "setShowSoftInputOnFocus", boolean.class);
public static Method getSetShowSoftInputOnFocusMethod(Class<?> cls, String methodName, Class<?>... parametersType) {
Class<?> sCls = cls.getSuperclass();
while (sCls != Object.class) {
try {
return sCls.getDeclaredMethod(methodName, parametersType);
} catch (NoSuchMethodException e) {
// Just super it again
}
sCls = sCls.getSuperclass();
}
return null;
}
private Context mContext;
/**
* Listener for Copy, Cut and Paste event
* Currently callback only for Paste event is implemented
*/
private OnEditTextActionListener mOnEditTextActionListener;
public KeypadlessKeypad(Context context) {
super(context);
mContext = context;
init();
}
public KeypadlessKeypad(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public KeypadlessKeypad(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}
#Override
protected void onSelectionChanged(int selStart, int selEnd) {
super.onSelectionChanged(selStart, selEnd);
}
public final void appendText(CharSequence text) {
append(text, 0, text.length());
}
/***
* Initialize all the necessary components of TextView.
*/
private void init() {
setSingleLine(true);
synchronized (this) {
setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
setFocusableInTouchMode(true);
}
reflexSetShowSoftInputOnFocus(false); // Workaround.
// Ensure that cursor is at the end of the input box when initialized. Without this, the
// cursor may be at index 0 when there is text added via layout XML.
setSelection(getText().length());
}
#Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
hideKeyboard();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
final boolean ret = super.onTouchEvent(event);
// Must be done after super.onTouchEvent()
hideKeyboard();
return ret;
}
private void hideKeyboard() {
final InputMethodManager imm = ((InputMethodManager) getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE));
if (imm != null && imm.isActive(this)) {
imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
}
}
private void reflexSetShowSoftInputOnFocus(boolean show) {
if (mShowSoftInputOnFocus != null) {
invokeMethod(mShowSoftInputOnFocus, this, show);
} else {
// Use fallback method. Not tested.
hideKeyboard();
}
}
public static Object invokeMethod(Method method, Object receiver, Object... args) {
try {
return method.invoke(receiver, args);
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
return null;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int textViewWidth = View.MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
this.setMeasuredDimension(textViewWidth, height);
}
#Override
protected void onTextChanged(CharSequence text, int start, int before,
int after) {
super.onTextChanged(text, start, before, after);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
}
#Override
public boolean onTextContextMenuItem(int id) {
boolean consumed = super.onTextContextMenuItem(id);
switch (id) {
case android.R.id.paste:
if (mOnEditTextActionListener != null) {
mOnEditTextActionListener.onPaste();
}
break;
}
return consumed;
}
/**
* Setter method for {#link #mOnEditTextActionListener}
*
* #param onEditTextActionListener
* Instance of the {#link OnEditTextActionListener}
*/
public void setOnEditTextActionListener(OnEditTextActionListener onEditTextActionListener) {
this.mOnEditTextActionListener = onEditTextActionListener;
}
private Rect mRect = new Rect();
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = MotionEventCompat.getActionMasked(event);
int[] location = new int[2];
getLocationOnScreen(location);
mRect.left = location[0];
mRect.top = location[1];
mRect.right = location[0] + getWidth();
mRect.bottom = location[1] + getHeight();
int x = (int) event.getX();
int y = (int) event.getY();
if (action == MotionEvent.ACTION_DOWN && !mRect.contains(x, y)) {
InputMethodManager input = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
input.hideSoftInputFromWindow(getWindowToken(), 0);
}
return super.dispatchTouchEvent(event);
}
#Override
public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) {
// Since we're replacing the text every time we add or remove a
// character, only read the difference. (issue 5337550)
final int added = event.getAddedCount();
final int removed = event.getRemovedCount();
final int length = event.getBeforeText().length();
if (added > removed) {
event.setRemovedCount(0);
event.setAddedCount(1);
event.setFromIndex(length);
} else if (removed > added) {
event.setRemovedCount(1);
event.setAddedCount(0);
event.setFromIndex(length - 1);
} else {
return;
}
} else if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
// The parent EditText class lets tts read "edit box" when this View has a focus, which
// confuses users on app launch (issue 5275935).
return;
}
super.sendAccessibilityEventUnchecked(event);
}
/**
* Interface to get callback from the Edittext copy, cut and paste event
* For time being only the Paste Event callback is generated
*/
public interface OnEditTextActionListener {
/**
* If Edittext get paste event then this method will be called
*/
void onPaste();
}
}
In your xml you can give like this,
<[package name].KeypadlessKeypad
android:id="#+id/dialnumbertv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00000000"
android:cursorVisible="false"
android:ellipsize="start"
android:gravity="center"
android:inputType="phone"
android:singleLine="true"
android:textIsSelectable="true"
android:textSize="30sp"
android:textStyle="italic"
android:visibility="visible"/>
And in your fragment you can implement like this,
public void onViewCreated(final View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mDialNumbertv = view.findViewById(R.id.dialnumbertv);
mDialNumbertv.setCursorVisible(false);
mDialNumbertv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!isDigitsEmpty()) {
mDialNumbertv.setCursorVisible(true);
}
}
});
mDialNumbertv.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if (isDigitsEmpty()) {
mDialNumbertv.setCursorVisible(false);
}
// updateDeleteButton();
}
});
mDialNumbertv.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
// Ref https://android.googlesource.com/platform/packages/apps/Contacts/+/39948dc7e34dc2041b801058dada28fedb80c388/src/com/android/contacts/dialpad/DialpadFragment.java
// Right now EditText does not show the "paste" option when cursor is not visible.
// To show that, make the cursor visible, and return false, letting the EditText
// show the option by itself.
mDialNumbertv.setCursorVisible(true);
return false;
}
});
mDialNumbertv.setOnEditTextActionListener(
new KeypadlessKeypad.OnEditTextActionListener() {
#Override
public void onPaste() {
// If some content pasted on mDialNumbertv
// we need to run some search on Contact and Price
String mobileNumber = mDialNumbertv.getText().toString();
if (TextUtils.isEmpty(mobileNumber)) {
return;
}
// updateContactName(mobileNumber);
}
});
}
private KeypadlessKeypad mDialNumbertv;
private boolean isDigitsEmpty() {
return mDialNumbertv.length() == 0;
}
private void setClickedDigit(final String digitToSet) {
if (!TextUtils.isEmpty(digitToSet)) {
char digit = digitToSet.charAt(0);
String mobileNumber = mDialNumbertv.getText() + digitToSet;
mDialNumbertv.getText().insert(mDialNumbertv.getSelectionStart(), digitToSet);
// If the cursor is at the end of the text we hide it.
final int length = mDialNumbertv.length();
if (length == mDialNumbertv.getSelectionStart() && length == mDialNumbertv.getSelectionEnd()) {
mDialNumbertv.setCursorVisible(false);
}
}
}
I wanted the same behavior which I achieved as follows -
Make a custom class that will override 2 methods of AppCompatEditText.
class CustomEditText(context: Context?, attrs: AttributeSet) : AppCompatEditText(context, attrs) {
override fun onCheckIsTextEditor(): Boolean {
return true
}
override fun isTextSelectable(): Boolean {
return true
}
}
In the XML file, create EditText using this custom view.
<com.ui.custom.CustomEditText
android:id="#+id/et_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="none"
android:focusable="true"
android:gravity="center"
android:focusableInTouchMode="true"/>
Now, just add onFocusChangeListener and set editText.setKeyListener = null.
binding.etEmail.onFocusChangeListener = OnFocusChangeListener { v, hasFocus ->
if (hasFocus) {
binding.etEmail.keyListener = null
}
}
You can add the same on onTouch if that is the requirement.
The main issue here is that onCheckIsTextEditor() of View class always returns false, which leads to cursor never blinking or being visible even if setCursorVisible(true) was called in code.
I hope it helps.
You can use edittext.setselection(0)
or
maybe you can request focus using requestfocus()
I have a number of buttons in a LinearLayout.
The buttons have 1 weight, so they are placed evenly into the layout.
I want to do something like this
for (int i = 0; i < texts.size(); ++i) {
Button button = new Button(/*context*/);
button.setLayoutParams(/*WRAP_CONTENT, MATCH_PARENT, 1.0f*/)
button.setText(texts[i]);
float fontSize = 20.0f;
button.setTextSize();
while (textDoesNotFitIntoButton(button)) {
fontSize -= 2.0f;
button.setTextSize(fontSize);
}
linearLayout.addView(button);
}
How do I check if text does not fit into the button?
The point is to fill all the button with text in such a way, that text occupies all available space and gets neither truncated nor split into lines.
E.g.
String[] texts = new String[] { "0", "sin^-1" };
If I choose a single text size for "0" to occupy the whole space then I either get "sin" split into two lines "sin^" and "-1" or "0" appears smaller than it should.
I have edited the answer given by #Jaswanth Manigundan to make it extend from android.support.v7.widget.AppCompatButton. Have a look at the below scripts:
FontFitTextView.java
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
public class FontFitTextView extends android.support.v7.widget.AppCompatButton
{
private static final float THRESHOLD = 0.5f;
private enum Mode { Width, Height, Both, None }
private int minTextSize = 1;
private int maxTextSize = 50;
private Mode mode = Mode.None;
private boolean inComputation;
private int widthMeasureSpec;
private int heightMeasureSpec;
public FontFitTextView(Context context) {
super(context);
}
public FontFitTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FontFitTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray tAttrs = context.obtainStyledAttributes(attrs, R.styleable.FontFitTextView, defStyle, 0);
maxTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_maxTextSize, maxTextSize);
minTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_minTextSize, minTextSize);
tAttrs.recycle();
}
private void resizeText() {
if (getWidth() <= 0 || getHeight() <= 0)
return;
if(mode == Mode.None)
return;
final int targetWidth = getWidth();
final int targetHeight = getHeight();
inComputation = true;
float higherSize = maxTextSize;
float lowerSize = minTextSize;
float textSize = getTextSize();
while(higherSize - lowerSize > THRESHOLD) {
textSize = (higherSize + lowerSize) / 2;
if (isTooBig(textSize, targetWidth, targetHeight)) {
higherSize = textSize;
} else {
lowerSize = textSize;
}
}
setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerSize);
measure(widthMeasureSpec, heightMeasureSpec);
inComputation = false;
}
private boolean isTooBig(float textSize, int targetWidth, int targetHeight) {
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
measure(0, 0);
if(mode == Mode.Both)
return getMeasuredWidth() >= targetWidth || getMeasuredHeight() >= targetHeight;
if(mode == Mode.Width)
return getMeasuredWidth() >= targetWidth;
else
return getMeasuredHeight() >= targetHeight;
}
private Mode getMode(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY)
return Mode.Both;
if(widthMode == MeasureSpec.EXACTLY)
return Mode.Width;
if(heightMode == MeasureSpec.EXACTLY)
return Mode.Height;
return Mode.None;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(!inComputation) {
this.widthMeasureSpec = widthMeasureSpec;
this.heightMeasureSpec = heightMeasureSpec;
mode = getMode(widthMeasureSpec, heightMeasureSpec);
resizeText();
}
}
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
resizeText();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh)
resizeText();
}
public int getMinTextSize() {
return minTextSize;
}
public void setMinTextSize(int minTextSize) {
this.minTextSize = minTextSize;
resizeText();
}
public int getMaxTextSize() {
return maxTextSize;
}
public void setMaxTextSize(int maxTextSize) {
this.maxTextSize = maxTextSize;
resizeText();
}
}
style.xml:
<resources>
<declare-styleable name="FontFitTextView">
<attr name="minTextSize" format="dimension" />
<attr name="maxTextSize" format="dimension" />
</declare-styleable>
</resources>
And in my activity_main.xml, I used the FontFitTextView tag instead of Button:
<com.example.myname.testsidebar.FontFitTextView
android:id="#+id/userDetailsBtn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:drawableTop="#drawable/user_details"
android:gravity="center"
android:background="#00FFFFFF"
android:paddingTop="10sp"
android:textSize="15sp"
android:text="Your Details"
android:drawableTint="#ED1B2F"
android:textColor="#797369"
android:layout_weight="1"
android:layout_gravity="center"/>
Hope it helps.
I searched the topic for several days, but finally confused. The idea is to create own alarm app with some special tricks. Firstly, I need clock hands. For creating custom clock, I used own class, which extends View. Hour and minute hands are PNG images.
They should be located in the center of the screen, but they dont. Actually, I can't even see them. And that is the question.
Here is the Clock class
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
//import android.os.Handler;
import android.text.format.Time;
import android.util.AttributeSet;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
#SuppressLint("NewApi")
public class Clock extends View {
public Clock(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
private Drawable mHourHand;
private Drawable mMinuteHand;
private boolean mAttached;
static private float mMinutes;
static private float mHour;
private boolean mChanged;
Context mContext;
private boolean mSeconds;
// Gettes & setters. These clock must present alarm time which user sets in the next view
protected float getmMinutes() {
return mMinutes;
}
protected static void setmMinutes(float mMinutes) {
Clock.mMinutes = mMinutes;
}
protected float getmHour() {
return mHour;
}
protected static void setmHour(float mHour) {
Clock.mHour = mHour;
}
private Point size;
// ctors
public Clock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Clock(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
Resources r = context.getResources();
TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.AnalogClock, defStyle, 0);
mContext=context;
mHourHand = r.getDrawable(R.drawable.hours);
mMinuteHand = r.getDrawable(R.drawable.minuts);
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mAttached) {
mAttached = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
// getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = 150; // and yes, 150 what? px, inches, dpi-s? I draw it just randomly
int desiredHeight = 150;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
setMeasuredDimension(width, height);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mChanged = true;
}
#SuppressWarnings({ "deprecation" })
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
boolean changed = mChanged;
if (changed) {
mChanged = false;
}
boolean seconds = mSeconds;
if (seconds ) {
mSeconds = false;
}
int w = 100; //These are too made randomly
int h = 100;
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
// if (android.os.Build.VERSION.SDK_INT <= 13)
// {
w = display.getWidth(); // deprecated
h = display.getHeight(); // deprecated
//}
// else if (android.os.Build.VERSION.SDK_INT > 13)
//{
//size = null;
//display.getSize(size);
//w = size.x;
//h = size.y;
//} ... I cant figure out, why, but size returns null. So I'll use deprecated ones just for
now.
// **Here are my measures. I suggest that if height of an hour hand should be about 1/4 of
screen width, then following the proportion - width of that hand should be old width*new
height/old height, or smthng**
int sizeXHour = w/3;
int sizeYHour = mHourHand.getIntrinsicHeight()*sizeXHour/mHourHand.getIntrinsicWidth();
int xHour = sizeXHour/2;
int yHour = sizeYHour/2;
canvas.rotate(mHour / 12.0f * 360.0f, xHour, yHour);
final Drawable hourHand = mHourHand;
if (changed) {
hourHand.setBounds((w / 2) - xHour, (h / 2) - yHour, sizeXHour, sizeYHour);
}
hourHand.draw(canvas);
canvas.restore();
int sizeYMinute = h/4;
int sizeXMinute = mMinuteHand.getIntrinsicWidth()*sizeYMinute/mMinuteHand.getIntrinsicHeight();
int xMinute = sizeXMinute/2;
int yMinute = sizeYMinute/2;
canvas.save();
canvas.rotate(mMinutes / 60.0f * 360.0f, xMinute, yMinute);
final Drawable minuteHand = mMinuteHand;
if (changed) {
minuteHand.setBounds((w / 2 - xMinute), (h / 2 - yMinute), sizeXMinute, sizeYMinute);
}
minuteHand.draw(canvas);
canvas.restore();
canvas.save();
}
I feel that something is very wrong, but can not figure what. The XML is:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="ee.st.running.dreamyclock.MainActivity"
android:background = "#drawable/clockk" >
<View
android:id="#+id/view1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<st.running.dreamyclock.Clock
android:id="#+id/clock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical = "true"
android:layout_marginTop="124dp" />
</RelativeLayout>
the attrs are:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="AnalogClock">
<attr name="hand_hour" format="reference"/>
<attr name="hand_minute" format="reference"/>
</declare-styleable>
</resources>
Any ideas where it came from? Thank you
Was this ever working? Try to add this to Clock constructor:
setWillNotDraw(false);
"If this view doesn't do any drawing on its own, set this flag to allow further optimizations. By default, this flag is not set on View, but could be set on some View subclasses such as ViewGroup. Typically, if you override onDraw(android.graphics.Canvas) you should clear this flag."
Edit:
If you want to re draw your view, you should call invalidate() method
"Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future. "
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mChanged = true;
this.invalidate();
}
You don't even have to call invalidate(); if you just want to for example rotate the view:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mChanged = true;
setRotation(20);
}
Edit 2:
When you are setting bounds for a Drawable, the "right" value should be higher than "left" for example:
minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2) + 4, y + (h / 4));
or
minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2) + minuteHand.getIntrinsicWidth(), y + (h / 4));
I want the text inside my TextView to fit the screen. I need to implement something like this:
The letter 'A' needs to be at the center of the screen with height equal to the height of the device screen. I have created a Custom TextView for this as follows but that doesn't seem to work. What I mean by this is that my text (letter A) isn't fitting the height of the screen. I tried manually adjusting the text font size but that isn't the right way I guess. Can someone point out a better solution for this?
package com.example;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class FontFitTextView extends TextView
{
private Paint mTestPaint;
private float maxFontSize;
private static final float MAX_FONT_SIZE_DEFAULT_VALUE = 20f;
public FontFitTextView(Context context)
{
super(context);
initialise(context, null);
}
public FontFitTextView(Context context, AttributeSet attributeSet)
{
super(context, attributeSet);
initialise(context, attributeSet);
}
public FontFitTextView(Context context, AttributeSet attributeSet, int defStyle)
{
super(context, attributeSet, defStyle);
initialise(context, attributeSet);
}
private void initialise(Context context, AttributeSet attributeSet)
{
if(attributeSet!=null)
{
TypedArray styledAttributes = context.obtainStyledAttributes(attributeSet, R.styleable.FontFitTextView);
maxFontSize = styledAttributes.getDimension(R.styleable.FontFitTextView_maxFontSize, MAX_FONT_SIZE_DEFAULT_VALUE);
styledAttributes.recycle();
}
else
{
maxFontSize = MAX_FONT_SIZE_DEFAULT_VALUE;
}
mTestPaint = new Paint();
mTestPaint.set(this.getPaint());
//max size defaults to the initially specified text size unless it is too small
}
private void refitText(String text, int textWidth, int textHeight)
{
if (textWidth <= 0)
return;
int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
float hi = maxFontSize;
float lo = 2;
final float threshold = 1f; // How close we have to be
mTestPaint.set(this.getPaint());
Rect bounds = new Rect();
while ((hi - lo) > threshold)
{
float size = (hi + lo) / 2;
mTestPaint.setTextSize(size);
mTestPaint.getTextBounds(text, 0, text.length(), bounds);
if (bounds.width() >= targetWidth || bounds.height() >= targetHeight)
hi = size; // too big
else
lo = size; // too small
}
// Use lo so that we undershoot rather than overshoot
this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int height = getMeasuredHeight();
refitText(this.getText().toString(), parentWidth, height);
this.setMeasuredDimension(parentWidth, height);
}
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after)
{
refitText(text.toString(), this.getWidth(), this.getHeight());
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
if (w != oldw)
{
refitText(this.getText().toString(), w, h);
}
}
}
XML file
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:res-auto="http://schemas.android.com/apk/res-auto"
android:id="#+id/home_Layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:id="#+id/linear1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_vertical">
<com.example.FontFitTextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="1"
android:textSize="80sp"
res-auto:maxFontSize="55sp" />
</LinearLayout>
</RelativeLayout>
Try calling super.onMeasure() method at the end of the onMeasure() method with the updated width and height (parentWidth, height).