How to create a bounce ScrollView in Android like iPhone?
Add effect bounce to scrollview in android
Step 1: Create new file BounceScrollView in package com.base.view
public class BounceScrollView extends ScrollView
{
private static final int MAX_Y_OVERSCROLL_DISTANCE = 200;
private Context mContext;
private int mMaxYOverscrollDistance;
public BounceScrollView(Context context)
{
super(context);
mContext = context;
initBounceScrollView();
}
public BounceScrollView(Context context, AttributeSet attrs)
{
super(context, attrs);
mContext = context;
initBounceScrollView();
}
public BounceScrollView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
mContext = context;
initBounceScrollView();
}
private void initBounceScrollView()
{
//get the density of the screen and do some maths with it on the max overscroll distance
//variable so that you get similar behaviors no matter what the screen size
final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
final float density = metrics.density;
mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE);
}
#Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)
{
//This is where the magic happens, we have replaced the incoming maxOverScrollY with our own custom variable mMaxYOverscrollDistance;
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxYOverscrollDistance, isTouchEvent);
}
}
Step 2: At your layout, please change
<ScrollView
android:id="#+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
to
<com.base.view.BounceScrollView
android:id="#+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
I have improved version of this answer: https://stackoverflow.com/a/13391248/3256989 .
So my version is (example for HorizontalScrollView):
public class HorizontalOverScrollView extends HorizontalScrollView {
private static final int WIDTH_DEVIDER_OVERSCROLL_DISTANCE = 3;
private TimeInterpolator mInterpolator;
private int mMaxOverscrollDistance;
private int mAnimTime;
private long mStartTime;
/**
* Instantiates {#link HorizontalOverScrollView} object.
*/
public HorizontalOverScrollView(final Context context) {
super(context);
init();
}
/**
* Instantiates {#link HorizontalOverScrollView} object.
*/
public HorizontalOverScrollView(final Context context, final AttributeSet attrs) {
super(context, attrs);
init();
}
/**
* Instantiates {#link HorizontalOverScrollView} object.
*/
public HorizontalOverScrollView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
final int widthPixels = getContext().getResources().getDisplayMetrics().widthPixels;
mMaxOverscrollDistance = widthPixels / WIDTH_DEVIDER_OVERSCROLL_DISTANCE;
mAnimTime = getContext().getResources().getInteger(android.R.integer.config_mediumAnimTime);
mInterpolator = new DecelerateInterpolator();
}
#Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
int overScrollDistance = mMaxOverscrollDistance;
if (isTouchEvent) {
mStartTime = AnimationUtils.currentAnimationTimeMillis();
} else {
final long elapsedTime = AnimationUtils.currentAnimationTimeMillis() - mStartTime;
float interpolation = mInterpolator.getInterpolation((float) elapsedTime / mAnimTime);
interpolation = interpolation > 1 ? 1 : interpolation;
overScrollDistance -= overScrollDistance * interpolation;
overScrollDistance = overScrollDistance < 0 ? 0 : overScrollDistance;
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, overScrollDistance, maxOverScrollY, isTouchEvent);
}
}
It's called overscroll in Android.
See http://developer.android.com/reference/android/widget/OverScroller.html and
(for example) http://developer.android.com/reference/android/widget/ListView.html#setOverscrollFooter(android.graphics.drawable.Drawable)
It's only available from API level 9 onward.
However, Samsung devices do seem to support overscroll natively in Android 2.2
Just use OverScrollDecoratorHelper
ScrollView scrollView = (ScrollView) findViewById(R.id.scroll_view);
OverScrollDecoratorHelper.setUpOverScroll(scrollView);
HorizontalScrollView horizontalScrollView = (HorizontalScrollView) findViewById(R.id.horizontal_scroll_view);
OverScrollDecoratorHelper.setUpOverScroll(horizontalScrollView);
https://github.com/EverythingMe/overscroll-decor
Related
I'm using androidx.emoji.widget.EmojiEditText. I want to force user to enter only emojis here. And limit him to max 3 emojis. How can I do something like this? I tried to use external emoji keyboard that cancels the soft keyboard and shows up but didn't work properly.
<androidx.emoji.widget.EmojiEditText
android:id="#+id/etEmoji"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_margin="10dp"
android:hint="Enter Emoji"
android:inputType="textShortMessage"
android:background="#android:color/transparent"/>
Below class will only allow Emojis and symbols.
public class CustomEditText extends EditText {
public CustomEditText(Context context) {
super(context);
init();
}
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setFilters(new InputFilter[]{new EmojiIncludeFilter()});
}
private class EmojiIncludeFilter implements InputFilter {
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
for (int i = start; i < end; i++) {
int type = Character.getType(source.charAt(i));
if (type != Character.SURROGATE && type != Character.OTHER_SYMBOL) {
// Other then emoji value will be blank
return "";
}
}
return null;
}
}
}
My custom view (which is a button) seems to be reinitialized when I change tab and come back to the one where my custom view is.
Before changing tab (when I launch the app):
After changing:
Find below the code of my custom view (without custom public methods):
public class ButtonView extends RelativeLayout {
private RelativeLayout mRelativeContainer;
private TextView mTextViewButton;
private ImageView mImageViewArrow;
private DiagonalLayout mDiagonalLayout;
private RelativeLayout mRelativeLayoutDiagonalColor;
private int mDirection;
private String mText;
private float mTextSize;
private int mTextColor;
private int mButtonColor;
private int mObliqueColor;
public ButtonView(Context context) {
super(context);
customLabel(context, null);
init(context);
}
public ButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
customLabel(context, attrs);
init(context);
}
public ButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
customLabel(context, attrs);
init(context);
}
private void init(Context context) {
switch (mDirection) {
case DIRECTION_RIGHT:
inflate(context, R.layout.button_view_right, this);
break;
case DIRECTION_LEFT:
inflate(context, R.layout.button_view_left, this);
break;
}
mRelativeContainer = findViewById(R.id.container);
mTextViewButton = findViewById(R.id.text);
mImageViewArrow = findViewById(R.id.arrow);
mDiagonalLayout = findViewById(R.id.diagonal);
mRelativeLayoutDiagonalColor = findViewById(R.id.diagonal_color);
mRelativeContainer.setBackgroundResource(R.drawable.bg_button_view);
setText(mText);
setTextSize(mTextSize);
setTextColor(mTextColor);
setButtonColor(mButtonColor);
setObliqueColor(mObliqueColor);
}
private void customLabel(Context context, AttributeSet attrs) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.ButtonView,
0, 0);
try {
mDirection = typedArray.getInt(R.styleable.ButtonView_direction, DIRECTION_RIGHT);
mText = typedArray.getString(R.styleable.ButtonView_text);
mTextSize = typedArray.getDimensionPixelSize(R.styleable.ButtonView_text_size, 52);
mTextColor = typedArray.getColor(R.styleable.ButtonView_text_color, getResources().getColor(android.R.color.white));
mButtonColor = typedArray.getColor(R.styleable.ButtonView_button_color, getResources().getColor(R.color.blue));
mObliqueColor = typedArray.getColor(R.styleable.ButtonView_oblique_color, getResources().getColor(R.color.dark_blue));
} finally {
typedArray.recycle();
}
}
private int getPercent(int i, int percent) {
return i * percent / 100;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int marginTopBottom = getPercent(mTextViewButton.getMeasuredHeight(), 50);
int marginStartEnd = getPercent(mTextViewButton.getMeasuredHeight(), 70);
LinearLayout.LayoutParams textViewButtonParams = (LinearLayout.LayoutParams) mTextViewButton.getLayoutParams();
textViewButtonParams.setMargins(
marginStartEnd, marginTopBottom,
marginStartEnd, marginTopBottom);
mTextViewButton.setLayoutParams(textViewButtonParams);
/***************************************************************/
((LinearLayout.LayoutParams) mImageViewArrow.getLayoutParams()).width = getPercent(mTextViewButton.getMeasuredHeight(), 60);
((LinearLayout.LayoutParams) mImageViewArrow.getLayoutParams()).height = getPercent(mTextViewButton.getMeasuredHeight(), 60);
switch (mDirection) {
case DIRECTION_RIGHT:
((LinearLayout.LayoutParams) mImageViewArrow.getLayoutParams()).setMargins(0, 0, marginStartEnd, 0);
mDiagonalLayout.getLayoutParams().width = getMeasuredWidth();
mDiagonalLayout.getLayoutParams().height = getPercent(mTextViewButton.getMeasuredHeight(), 35);
((RelativeLayout.LayoutParams) mDiagonalLayout.getLayoutParams()).setMargins(
0,
marginTopBottom * 2 + mTextViewButton.getMeasuredHeight() - mDiagonalLayout.getLayoutParams().height,
0,
0);
break;
case DIRECTION_LEFT:
((LinearLayout.LayoutParams) mImageViewArrow.getLayoutParams()).setMargins(marginStartEnd, 0, 0, 0);
mDiagonalLayout.getLayoutParams().width = getPercent(mTextViewButton.getMeasuredHeight(), 60) * 4;
mDiagonalLayout.getLayoutParams().height = getPercent(mTextViewButton.getMeasuredHeight(), 35);
((RelativeLayout.LayoutParams) mDiagonalLayout.getLayoutParams()).setMargins(
0,
marginTopBottom * 2 + mTextViewButton.getMeasuredHeight() - mDiagonalLayout.getLayoutParams().height,
0,
0);
break;
}
}
}
I have no idea of what happens and I didn't find any proper answers so far.
I'm trying to recreate the game 2048.
When I'm clicking on the button "New Game", I start a new intent with the PlayActivity. The button starts the activity, but when the view shows, the grid layout is not visible...
The view is a textview and then the GridLayout (which is custom, because I have to populate it with custom tiles)...
The code:
PlayActivity.java
public class PlayActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
}
}
activity_play.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<TextView
android:id="#+id/txtScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/score"
android:background="#color/dark_grey"/>
<com.charlotteerpels.oefening1.Board
android:id="#+id/board"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.charlotteerpels.oefening1.Board>
</LinearLayout>
Board.java
public class Board extends GridLayout {
Card[][] cardBoard;
private int cardHeight;
private int cardWidth;
public void initBoard(Context context) {
this.setColumnCount(4);
this.setRowCount(4);
this.cardBoard = new Card[4][4];
calculateCardSpecs(context);
populateBoard();
}
private void calculateCardSpecs(Context context) {
WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
this.cardWidth = width/4;
this.cardHeight = this.cardWidth;
}
private void populateBoard() {
int tilesWithNumber = 0;
Random random = new Random();
for(int i=0; i<4; i++) {
for(int j=0; j<4; j++) {
Card card = new Card(getContext());
if(random.nextInt(2) == 0) {
if(tilesWithNumber<2) {
card.setTextVisible();
tilesWithNumber++;
}
}
cardBoard[i][j] = card;
}
}
addCardsToView();
}
private void addCardsToView() {
for(int i=0; i<4; i++) {
for(int j=0; j<4; j++) {
Card card = cardBoard[i][j];
addView(card, cardWidth, cardHeight);
}
}
}
public Board(Context context) {
super(context);
initBoard(context);
}
public Board(Context context, AttributeSet attrs) {
super(context, attrs);
initBoard(context);
}
public Board(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initBoard(context);
}
}
Card.java
public class Card extends FrameLayout {
Random randomGenerator;
private int number;
Resources res = getResources();
private TextView lblNumber;
public void setNumber(int number) {
this.number = number;
}
public int getNumber() {
return this.number;
}
private void initCard(Context context) {
lblNumber = new TextView(context);
lblNumber.setBackgroundColor(res.getColor(R.color.light_brown));
randomGenerator = new Random();
int r = randomGenerator.nextInt(2);
if(r == 0)
this.number = 2;
else
this.number = 4;
}
public void setTextVisible() {
lblNumber.setText(String.valueOf(number));
}
public Card(Context context) {
super(context);
initCard(context);
}
public Card(Context context, AttributeSet attrs) {
super(context, attrs);
initCard(context);
}
public Card(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initCard(context);
}
}
Does anyone have an idea of what I'm doing wrong?
Thanks in advance!
EDIT: RobVoisey found the answer, I created the TextView lblNumber in the class Card, but I didn't added it to the Card...
So in the class Card in the method initCard, I added after the if/else the textView lblNumber to the Card layout:
private void initCard(Context context) {
lblNumber = new TextView(context);
lblNumber.setBackgroundColor(res.getColor(R.color.light_brown));
randomGenerator = new Random();
int r = randomGenerator.nextInt(2);
if(r == 0)
this.number = 2;
else
this.number = 4;
addView(lblNumber);
}
I'm trying to port some Java code in to C#. where CW is a class which extends a view. OnSelectedListener is a interface with Cselected as method which takes an int argument.
setListener is a method within the class. the problem is with instantiate a interface like in Java.
private View selectedView = new View( context );
CW.setListener( new OnSelectedListener() {
#Override
public void cSelected(Integer color) {
selectedColor = color;
selectedView.setBackgroundColor( color );
}
});
Another Implementation in same method
VS.setListener( new OnSelectedListener() {
public void cSelected(Integer color) {
VS.setColor( color, true );
}
} );
Can anyone please help me port the above code to C#? Any help is appreciated. I'm using Xamarin to develop Android apps.
EDIT:
Here is the full CW class
public class HSVColorWheel : View
{
private const float SCALE = 2f;
private const float FADE_OUT_FRACTION = 0.03f;
private const int POINTER_LINE_WIDTH_DP = 2;
private const int POINTER_LENGTH_DP = 10;
private Context _context;
public HSVColorWheel(Context context, IAttributeSet attrs, int defStyle)
: base(context, attrs, defStyle)
{
this._context = context;
Init();
}
public HSVColorWheel(Context context, IAttributeSet attrs) : base(context, attrs)
{
this._context = context;
Init();
}
public HSVColorWheel(Context context) : base(context)
{
this._context = context;
Init();
}
private int scale;
private int pointerLength;
private int innerPadding;
private Paint pointerPaint = new Paint();
private void Init()
{
float density = _context.Resources.DisplayMetrics.Density;
scale = (int) (density*SCALE);
pointerLength = (int) (density*POINTER_LENGTH_DP);
pointerPaint.StrokeWidth = (int) (density*POINTER_LINE_WIDTH_DP);
innerPadding = pointerLength/2;
}
public void setListener(OnSelectedListener listener)
{
_listener = listener;
}
private float[] colorHsv = {0f, 0f, 1f};
public void setColor(Color color)
{
Color.ColorToHSV(color, colorHsv);
Invalidate();
}
}
Interface:
public interface OnSelectedListener {
void cSelected( Integer color );
}
As mentioned in the comments, since C# has language-level support for events, it provides a much cleaner approach than java's "even listener" approach.
Therefore, all listener-based java code should be converted into proper events in C#.
In this case, you're seemingly raising an event that has an int parameter. This is declared in C# like so:
//In the CW class:
public event EventHandler<int> SelectionChanged;
and then raised via an "event invocator", like so:
//In the CW class:
public void OnSelectionChanged()
{
var handler = SelectionChanged;
if (handler != null)
handler(this, //[ some int value here ]);
}
from the "consumer", or "listener" side, you simply handle the event:
//In an Activity
var CW = new CW(this);
CW.SelectionChanged += CW_SelectionChanged;
where CW_SelectionChanged can either be a an anonymous method, an actual named method, or even a lambda expression:
CW.SelectionChanged += (sender, intValue) => //[here you do something with intValue]
// -- OR --
CW.SelectionChanged += this.CW_SelectionChanged;
// then
private void CW_SelectionChanged(object sender, int intValue)
{
//[here you do something with intValue]
}
This way, you don't need to declare additional, unneeded 1-method interfaces.
Using onDraw, I want to make a custom text view that changes color depending on its text value. For example, if the text value is "hello" I want it to be red and if it says "bye" I want it to be green. Any helps greatly appreciated.
I'm not necessarily sure why you want to do this in onDraw(). Unless you have a really good reason to set up a custom TextView/EditText, that's not necessary.
To simplify your situation, you can implement a TextWatcher to do this, and in onTextChanged(), you can set the color by comparing the string values using .equals().
Here is an example of your theoretical situation:
final EditText yourEditText = /* findViewById maybe? */;
yourEditText.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.equalsIgnoreCase("hello"))
yourEditText.setTextColor(Color.RED);
else if (s.equalsIgnoreCase("bye"))
yourEditText.setTextColor(Color.GREEN);
else // if it says neither "hello" nor "bye"
yourEditText.setTextColor(Color.BLACK);
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Nothing needs to happen here
}
public void afterTextChanged(Editable s) {
// Nothing needs to happen here
}
});
If you feel its necessary to maintain this in onDraw(), simply extract the code from onTextChanged() and change yourEditText to this, or place it in the constructor instead:
public class YourTextView extends TextView { // Or extends EditText, doesn't matter
public YourTextView(Context context) {
this(context, null, 0);
}
public YourTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public YourTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
addTextChangedListener(new TextWatcher() {
// Copy the TextWatcher code from the example above, replacing "yourEditText" with "YourTextView.this"
});
}
// ... Rest of your class
}
I figured out how to do it in a more creative way using onDraw.
public class MagnitudeTextView extends TextView {
public MagnitudeTextView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public MagnitudeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MagnitudeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
/*
* (non-Javadoc)
*
* #see android.widget.TextView#onDraw(android.graphics.Canvas)
*/
#Override
protected void onDraw(Canvas canvas) {
int height = getMeasuredHeight();
int width = getMeasuredWidth();
int px = width / 2;
int py = height / 2;
Paint Red = new Paint(Paint.ANTI_ALIAS_FLAG);
Red.setColor(Color.RED);
Paint White = new Paint(Paint.ANTI_ALIAS_FLAG);
White.setColor(Color.DKGRAY);
Paint Yellow = new Paint(Paint.ANTI_ALIAS_FLAG);
Yellow.setARGB(210, 105, 30, 0);
Paint Blue = new Paint(Paint.ANTI_ALIAS_FLAG);
Blue.setColor(Color.BLUE);
float textWidth = Red.measureText(String.valueOf(getText()));
String g = String.valueOf(getText());
if (g.startsWith("3") || g.startsWith("4")) {
canvas.drawText(String.valueOf(getText()), px - textWidth / 2, py,
White);
}
if (g.startsWith("6") || g.startsWith("5") || g.startsWith("7")
|| g.startsWith("8")) {
canvas.drawText(String.valueOf(getText()), px - textWidth / 2, py,
Yellow);
}
if (g.startsWith("9") || g.startsWith("10")) {
canvas.drawText(String.valueOf(getText()), px - textWidth / 2, py,
Red);
}
// super.onDraw(canvas);
}
}
You can overwrite setText() and set the color using setTextColor().
You can do it inside onDraw as well, but it's not worth the weigth, as it may pass many times inside onDraw.
You can implement TextWatcher and use onTextChanged()
More about it here in the Android Docs
Use this to get the text:
TextView text = (TextView)findViewById(R.id.textid);
String value = text.getText().toString();
Then check what the text is and change the color :
if (value.equals("hello")) {
text.setBackgroundColor(yourcolor);
}