Customizing the ScrollBar programmatically - android

My requirements are to display a vertical list of views with a scrollbar whenever the total height of the views is bigger than the allowed height for the list.
Additionally, I need to customize the scrollBar appearance (background and thumb) programmatically. (At runtime, my activity will receive all the data to do the rendering : the bitmap to use as scrollbar background, the scrollbar width and the bitmap to use as scrollbar's thumb.)
A ListView seems a good candidate for this, except that I can't find a way to customize the scrollBar programmatically.
I read this question scrollBar in a listView...customizing it. but, it's using a theme, and AFAIK it's not possible the create a new theme programmatically.
So my question(s):
Is there a way to customize the appearance of a scrollbar (within a ListView) programmatically ?
[only if the answer to the first one is "definitively not possible"] do you see any other way to achieve those requirements (and some working example of doing it)
Thanks.
EDIT (23/12)
I found this hidden class android.widget.ScrollBarDrawable. May be a good starting point to build a solution (no time to investigate it right now).

is there a way to customize the appearance of a scrollbar (within a ListView) programmatically ?
Literally, it does not look like it, simply because there are no setters for manipulating it.
It is conceivable that you could create your own BenView subclass of View that replaces the hidden ScrollBarDrawable with BenScrollBarDrawable that handles dynamic changes. But then you would need to create BenViewGroup, BenAbsListView, and BenListView, cloning their Ben-less counterparts' source code, to have them chain up to BenView to inherit this behavior. And, since one of ViewGroup, AbsListView, or ListView is bound to change in any Android release, you will actually need N copies of these classes and choose the right copy based on API level at runtime. And then your app may still behave oddly on some devices where the manufacturer went in and tinkered with ViewGroup, AbsListView, or ListView, and you will not be using their code.
I doubt that this is worth it, particularly since scrollbars are not visible by default except while scrolling.
do you see any other way to achieve those requirements (and some working example of doing it)
Handle your own touch events to do your own scrolling and do your own scrollbar stuff. Whether this is less work than the above solution is up for debate.

I just faced the same problem for RecyclerView and solved it like this
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
/**
* Created on 22.3.2016.
*
* #author Bojan Kseneman
* #description A recycler view that will draw the scroll bar with a different color
*/
public class CustomScrollBarRecyclerView extends RecyclerView {
private int scrollBarColor = Color.RED;
public CustomScrollBarRecyclerView(Context context) {
super(context);
}
public CustomScrollBarRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomScrollBarRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setScrollBarColor(#ColorInt int scrollBarColor) {
this.scrollBarColor = scrollBarColor;
}
/**
* Called by Android {#link android.view.View#onDrawScrollBars(Canvas)}
**/
protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) {
scrollBar.setColorFilter(scrollBarColor, PorterDuff.Mode.SRC_ATOP);
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
}
/**
* Called by Android {#link android.view.View#onDrawScrollBars(Canvas)}
**/
protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) {
scrollBar.setColorFilter(scrollBarColor, PorterDuff.Mode.SRC_ATOP);
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
}
}
These methods are defined in View class, so the same princible should work of other views like ScrollView and ListView.

Just change the android:scrollbarThumbVertical="#drawable/scrollbar_vertical_thumb"
and create a drawable as per your need.
Like
<ListView android:scrollbarThumbVertical="#drawable/scrollbar_vertical_thumb" />
in the Drawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient android:angle="0" android:endColor="#6699FF" android:startColor="#3333FF" />
<corners android:radius="1dp" />
<size android:width="1dp" />
</shape>
I think this is what you want!

If you only need to support API 24 or later, you can use a custom drawable to accomplish this task. You'll need to decide how you're going to allow the drawable to communicate with the activity. Here is one possible solution.
res/drawable/my_custom_scrollbar_thumb.xml
<com.example.RegisteredMutableDrawable android:id="#+id/scroll_thumb" />
res/drawable/my_custom_scrollbar_track.xml
<com.example.RegisteredMutableDrawable android:id="#+id/scroll_track" />
somewhere in your layout file
<ListView
android:scrollbarTrackVertical="#drawable/my_custom_scrollbar_track"
android:scrollbarThumbVertical="#drawable/my_custom_scrollbar_thumb"
the drawable implementation
public class RegisteredMutableDrawable extends DrawableWrapper
{
public static interface Registrar
{
public void register(int _id, RegisteredMutableDrawable _drawable);
}
public static Registrar registrar;
private static int[] ATTRS = new int[] { android.R.attr.id };
public RegisteredMutableDrawable()
{
super(null);
}
public void inflate(Resources _res, XmlPullParser _parser, AttributeSet _as, Theme _theme)
throws XmlPullParserException, IOException
{
super.inflate(_res, _parser, _as, _theme);
final Registrar r = registrar;
if (r != null)
{
final TypedArray ta = _res.obtainAttributes(_as, ATTRS);
final int id = ta.getResourceId(0, 0);
ta.recycle();
r.register(id, this);
}
}
}
in your activity
public class MyActivity extends Activity implements RegisteredMutableDrawable.Registrar
{
#Override public void onCreate(Bundle _icicle)
{
super.onCreate(_icicle);
RegisteredMutableDrawable.registrar = this;
setContentView(R.layout.whatever);
RegisteredMutableDrawable.registrar = null; // don't leak our activity!
}
#Override public void register(int _id, RegisteredMutableDrawable _drawable)
{
switch(_id)
{
case R.id.scroll_thumb:
_drawable.setDrawable(myThumbDrawable);
break;
case R.id.scroll_track:
_drawable.setDrawable(myTrackDrawable);
break;
}
}
}

You can use the jquery plugin(jquery.nicescroll.js) to achieve this. More details visit
http://areaaperta.com/nicescroll/

Related

TextInputEditText drawable padding without hint [duplicate]

I have make an EditText inside TextInputLayout. I am setting a drawableLeft to an EditText at runtime in my code, but as soon as I add the drawableLeft the floating hint inside TextInputLayout shifts to right leaving the space equal to drawable width. But I dont want that space in hint, so help me to resolve this!!
TextInputLayout uses a helper class - CollapsingTextHelper - to manipulate its hint text. The instance of this helper is private, and none of the attributes associated with its layout are exposed, so we'll need to use a little reflection to get access to it. Furthermore, its properties are set and recalculated every time the TextInputLayout is laid out, so it makes sense to subclass TextInputLayout, override its onLayout() method, and make our adjustments there.
import android.content.Context;
import android.graphics.Rect;
import android.support.design.widget.TextInputLayout;
import android.util.AttributeSet;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class CustomTextInputLayout extends TextInputLayout {
private Object collapsingTextHelper;
private Rect bounds;
private Method recalculateMethod;
public CustomTextInputLayout(Context context) {
this(context, null);
}
public CustomTextInputLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
adjustBounds();
}
private void init() {
try {
Field cthField = TextInputLayout.class.getDeclaredField("mCollapsingTextHelper");
cthField.setAccessible(true);
collapsingTextHelper = cthField.get(this);
Field boundsField = collapsingTextHelper.getClass().getDeclaredField("mCollapsedBounds");
boundsField.setAccessible(true);
bounds = (Rect) boundsField.get(collapsingTextHelper);
recalculateMethod = collapsingTextHelper.getClass().getDeclaredMethod("recalculate");
}
catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) {
collapsingTextHelper = null;
bounds = null;
recalculateMethod = null;
e.printStackTrace();
}
}
private void adjustBounds() {
if (collapsingTextHelper == null) {
return;
}
try {
bounds.left = getEditText().getLeft() + getEditText().getPaddingLeft();
recalculateMethod.invoke(collapsingTextHelper);
}
catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
e.printStackTrace();
}
}
}
This custom class is a drop-in replacement for the regular TextInputLayout, and you would use it the same way. For example:
<com.mycompany.myapp.CustomTextInputLayout
android:id="#+id/text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Model (Example i10, Swift, etc.)"
app:hintTextAppearance="#style/TextLabel">
<android.support.design.widget.TextInputEditText
android:id="#+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableLeft="#drawable/bmw"
android:text="M Series" />
</com.mycompany.myapp.CustomTextInputLayout>
Notes:
In the move to the Material Components library, the field names for the helper class and the bounds have dropped the m prefix notation. As noted in comments, they are now named collapsingTextHelper and collapsedBounds, respectively.
As of API level 28 (Pie), there are certain Restrictions on non-SDK interfaces, including reflection, to access normally inaccessible members in the SDK. However, the various available documents seem to indicate that reflection on components within your own package are not prohibited. As the support library is not part of the platform SDK, and is merged into your package when built, this solution should still be valid. Indeed, recent testing has uncovered no issues, and this still works as expected on the available Pie emulators.
Yes, Its a common problem that I faced recently.I solved it with simple one line code:
Place a padding between your hint and drawbleleft by using drawble padding.
If you are adding drawble at runtime simply add drawblepadding in xml before hand or you can dynamically add drawble padding.
editText.setCompoundDrawablePadding(your padding value);
Try it and let me know. It worked for me.

Custom ImageButton, which method to override for simplest RadioButton interface?

I am in the process of making a custom view that is essentially an ImageButton with added logic so it also have the behavior of a RadioButton. All I want to do is have it built into the view that when the user clicks the button the image is changed, an internal boolean is marked true to note it is selected, and an interface method is called to let the RadioGroup it is a part of to unselect all the other views within it. I don't want to impact the existing behavior of the base ImageButton whatsoever.
I've only made one other custom view before and that was by following a tutorial almost exactly to the letter and since there are so many different methods inhereted from View that deal with clicks/touches (i.e. onTouch, onClick, motion event, etc.) taking it all in has left me a bit confused. I am fine writing the interface itself, its the modification of ImageButton where I'm not too sure how to attack it.
So, I ask you all: What method/methods do I need to override to add this simple functionality, while not impacting the current behavior of ImageButton, nor screwing up the ability to set an onTouchListener for the button that will perform additional actions on click without compromising this built in radio button logic? If I need to override something that will mess with the default behavior I mentioned, what do I need to put in the new method to restore that functionality?
This is what I have so far:
public class RadioImageButton extends AppCompatImageButton implements RadioCheckable {
//Default constructor
public RadioImageButton(Context context) {
super(context);
initView();
}
//Constructor with defined attributes
public RadioImageButton(Context context, AttributeSet attrs) {
super(context, attrs);
parseAttributes();
initView();
}
//Constructor with defined attributes and attributes taken from style defaults that aren't defined
public RadioImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//=========================================================================
// Setup
//=========================================================================
private void initView()
{
}
private void parseAttributes()
{
}
}
The approach I would like to take would be something like:
...All other code I already showed
mChecked = false;
#Overide
void onClick(...)
{
mChecked = true;
setImageSource(R.example.checked_image); // Or I can use a selector resource
*Call to Radio Interface*;
mOnTouchListener.onTouch(v, event); //Handle user onTouchListener
}
...
and leave all the other code alone, though I'm sure it isn't quite that simple.
I thought a good start would be trying to find the source code for the default ImageButton class and set mine up to be a near replica so I can understand how it works and then modify from there, but all I could really find was this:
https://android.googlesource.com/platform/frameworks/base/+/android-7.0.0_r35/core/java/android/widget/ImageButton.java
and there is no way that is the actual source because pressing Ctrl+O shows many more functions that ImageButton defines that are not inherited from another class; regardless, that link is not at all helpful as its basically a giant comment with little to no code.
Thanks for any suggestions that will help me accomplish this in the most straightforward way.
EDIT: #pskink - Looking through the code you provided, it seems like it is trying to generate a matrix in order to transform the provided drawable (src) so that it fits into a new rectangle (dst) while maintaining the aspect ratio and positioning (hence ScaleToFit.CENTER). I would assume the destination rectangle would be the bounds of the view the drawable is contained in, which in this case is the RadioButton, but while stepping through the override of the "draw()" method it doesn't quite seem to be doing that, though I'm not quite sure how cavas.concat(matrix) is resolved so I'm not positive. Regardless it doesn't seem to work as intended or I am somehow using it wrong.
While maybe not the most robust method, it seems like the most straightforward, yet effective way to handle what I wanted to do was to leverage the Matrix class and its powerful scaling/transformation tools, specifically "setRectToRect()". Creating a custom view that extends RadioButton instead of ImageButton allowed me to make use of the existing RadioGroup, while manipulating characteristics of the button's drawables in the new classes Constructor achieved the behavior I was looking for.
Custom RadioButton class:
public class RadioImageButton extends android.support.v7.widget.AppCompatRadioButton {
int stateDrawable; //Resource ID for RadioButton selector Drawable
D scaledDrawable; //Post-scaling drawable
public RadioImageButtonTwo(Context context) {
super(context);
initView();
}
public RadioImageButtonTwo(Context context, AttributeSet attrs) {
super(context, attrs);
parseAttributes(attrs);
initView();
}
private void parseAttributes(AttributeSet attrs)
{
TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs,R.styleable.RadioImageButtonTwo);
try {
// Obtain selector drawable from attributes
stateDrawable = styledAttrs.getResourceId(R.styleable.RadioImageButtonTwo_button_sDrawable, R.drawable.test_draw2);
} finally {
styledAttrs.recycle(); //Required for public shared view
}
}
private void initView()
{
scaledDrawable = new D(getResources(),stateDrawable); // Create scaled drawable
setBackground(scaledDrawable); // Apply scaled drawable
setButtonDrawable(android.R.color.transparent); // "Disable" button graphic
}
}
See more on setting up a custom view here: https://developer.android.com/training/custom-views/create-view#customattr
Custom drawable class "D" that includes fitCenter scaling thanks to #pskink:
class D extends StateListDrawable {
private Rect bounds = new Rect();
private RectF src = new RectF();
private RectF dst = new RectF();
private Matrix matrix = new Matrix();
public D(Resources r, int resId) {
try {
XmlResourceParser parser = r.getXml(resId);
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (type == XmlPullParser.START_TAG && parser.getName().equals("selector")) {
inflate(r, parser, Xml.asAttributeSet(parser));
break;
}
}
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
}
}
#Override
public void draw(Canvas canvas) {
Drawable current = getCurrent();
bounds.set(0, 0, current.getIntrinsicWidth(), current.getIntrinsicHeight());
current.setBounds(bounds);
src.set(bounds);
dst.set(getBounds());
matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
canvas.concat(matrix);
super.draw(canvas);
}
}
Note that for whatever reason setting the button drawable itself to this custom drawable breaks the scaling, so changing the background to the custom drawable and setting the button drawable to transparent was the only way this worked. This custom drawable could easily be expanded upon to have more scaling type options and another view attribute could be defined to allow the user to choose the scaling type through XML.
This custom ImageView that mimics the (pointed out by pskink aswell) could also prove helpful in this task, as it too utilizes the Matrix class to implement multiple types of image scaling: https://github.com/yqritc/Android-ScalableImageView

setting alpha value to ImageView

I am using the following code to set the alpha value of an ImageView (this should be compatible with all devices, even pre API 11)
AlphaAnimation alpha = new AlphaAnimation(0.85F, 0.85F);
alpha.setDuration(0); // Make animation instant
alpha.setFillAfter(true); // Tell it to persist after the animation ends
ImageView view = (ImageView) findViewById(R.id.transparentBackground);
view.startAnimation(alpha);
However, when I open the app on devices running on gingerbread and below, the imageView is completely transparent but on devices running on honeycomb or higher, the alpha value is set to .85 and the imageView is displayed perfectly.
How can make this happen on gingerBread as well?
An easy way to get this to work is to use the NineOldAndroids project which ports the Honeycomb animation API back to older versions including AlphaAnimation. See http://nineoldandroids.com/.
So, using the ObjectAnimator would be something like this:
ObjectAnimator.ofFloat(imageView, "alpha", .85f).start();
You have a function to do it (has been deprecated because of the new view's generic way of handling transparency but can be used safely on Android 2.x):
myImageView.setAlpha(128); // range [0, 255]
You'll have to implement a custom animation though. This can be done with a handler for instance:
Handler animationHandler = new Handler() {
public void handleMessage(Message msg) {
int currentAlpha = msg.arg1;
int targetAlpha = msg.arg2;
if (currentAlpha==targetAlpha) return;
else {
if (currentAlpha<targetAlpha) --currentAlpha;
else ++currentAlpha;
imageView.setAlpha(currentAlpha);
sendMessageDelayed(obtainMessage(0, currentAlpha, targetAlpha), 10);
}
}
}
// Show imageview
animationHandler.sendMessage(animationHandler.obtainMessage(0, currentAlpha, 255));
// Hide imageview
animationHandler.sendMessage(animationHandler.obtainMessage(0, currentAlpha, 0));
Above code is not memory-leak safe (handlers should be static and should keep weak references to context and views
You should improve it to allow controlling animation speed, ...
You need to keep current alpha around because imageView does not have a getAlpha method.
Ok, lets create an custom imageview, it solves the problem , by overriding the onDraw :)
AlphaImageView.java
package com.example.shareintent;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ImageView;
public class AlphaImageView extends ImageView {
public AlphaImageView(Context context) {
super(context);
}
public AlphaImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AlphaImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onDraw(Canvas canvas){
canvas.saveLayerAlpha(0, 0, canvas.getWidth(), canvas.getHeight(), 0x66, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
super.onDraw(canvas);
}
}
your activity xml
<com.example.shareintent.AlphaImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="52dp"
android:layout_marginTop="50dp"
android:src="#drawable/ic_launcher" />

Android: Using FEATURE_NO_TITLE with custom ViewGroup leaves space on top of the window

I am trying to create a custom ViewGroup, and I want to use it with a full screen application. I am using the "requestWindowFeature(Window.FEATURE_NO_TITLE)" to hide the title bar. The title bar is not showing, but it still consuming space on top of the window.
The image above was generated with the following code:
public class CustomLayoutTestActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
Button b = new Button(this);
b.setText("Hello");
CustomLayout layout = new CustomLayout(this);
layout.addView(b);
setContentView(layout);
}
}
public class CustomLayout extends ViewGroup {
public CustomLayout(Context context) {
super(context);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i("CustomLayout", "changed="+changed+" l="+l+" t="+t+" r="+r+" b="+b);
final int childCount = getChildCount();
for (int i = 0; i < childCount; ++i) {
final View v = getChildAt(i);
v.layout(l, t, r, b);
}
}
}
(The full Eclipse project is here)
It is interesting to see that it is the Android that is given this space for my custom layout. I am setting the CustomLayout as the root layout of my Activity. In the Log in the "onLayout" is receiving "t=25", and that is what is pushing my layout down. What I don't know is what I am doing wrong that makes Android the "t=25" (which is exactly the height of the title bar).
I am running this code in the Android SDK 2.1, but I also happens in Android 2.2.
EDIT: If I change the CustomLayout class for some default layout (such as LinearLayout), the space disappears. Of course, the default layouts of Android SDK don't create the layout I am trying to create, so that is why I am creating one.
Although the layout I am creating is somewhat complex, this is the smallest code I could create reproducing the problem I have with my layout.
It's not a full answer, but in the meantime you can work around the problem by wrapping your custom layout in a <FrameLayout />
Also, it's worth noting that your layout extends beyond the bottom of the screen. It's shifted down by the title bar height (38 pixels in my emulator)
Edit: Got it. onLayout() (and the corresponding layout() method) specify that the coordinate are not relative to the screen origin, they're relative to the parent ( http://developer.android.com/reference/android/view/View.html#layout%28int,%20int,%20int,%20int%29 ). So the system is telling you that you're at relative coordinates (0, 38), and you're adding it when passing that down to your child, which means that you're saying that your child is at screen coordinates (0, 76), causing the gap.
What you actually want to do is:
v.layout(0, 0, r - l, b - t);
That will put your child Views aligned with the top left corner of your View, with the same width and height as your view.
I had the same issue with a FrameLayout in 2.2
I fixed it by adding android:layout_gravity="top" to the FrameLayout

Is it possible to change the colour of the FadingEdge of a Listview?

I want to give the effect that the ListView has faded from whatever is around it. By default it is set to whatever colour your ListView is. I can adjust the orientation of the FadingEdge and the size of the FadingEdge but not the colour. Is it possible?
You'll need to create a new class that extends ListView.
package com.mypackage;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;
public class ColorFadeListView extends ListView
{
// fade to green by default
private static int mFadeColor = 0xFF00FF00;
public ColorFadeListView(Context context, AttributeSet attrs)
{
this(context, attrs,0);
}
public ColorFadeListView(Context context, AttributeSet attrs, int defStyle)
{
super(context,attrs,defStyle);
setFadingEdgeLength(30);
setVerticalFadingEdgeEnabled(true);
}
#Override
public int getSolidColor()
{
return mFadeColor;
}
public void setFadeColor( int fadeColor )
{
mFadeColor = fadeColor;
}
public int getFadeColor()
{
return mFadeColor;
}
}
You can use this list view identically to a normal ListView (though you'll have to cast it properly to use the fadeColor accessor methods). In your XML, instead of defining an object as <ListView android:properties.../> define it as <com.mypackage.ColorFadeListView android:properties.../>
Yes you can !
setCacheColorHint(Color.WHITE);
You can try this (it's a hack, I know):
int glowDrawableId = context.getResources().getIdentifier("overscroll_glow", "drawable", "android");
Drawable androidGlow = context.getResources().getDrawable(glowDrawableId);
androidGlow.setColorFilter(brandColor, PorterDuff.Mode.MULTIPLY);
I took advantage of the fact that the glow effect is actually a shared Drawable and applied a filter on it: http://evendanan.net/android/branding/2013/12/09/branding-edge-effect/

Categories

Resources