I am having problem inflating my CustomView programmatically from the XML layout that I specify.
I have a CustomView which extends RelativeLayout and contains another RelativeLayout which in turns contain 2 ImageView and 1 LinearLayout. The ImageViews are arrow Icons which I put to the left and right of the parent by android:layout_alignParentLeft="true" and android:layout_alignParentRight="true" respectively, and the LinearLayout is used to fill all the space in between.
To make it clear, here is the xml layout view in the Eclipse Layout Designer, which is what I intended it to be...
If I setContentView(R.layout.my_xml_layout); directly from the Activity, everything appears as shown in Eclipse Layout Designer, however, if I inflate the R.layout.my_xml_layout from my CustomView's constructor, there is a stubborn margin to the left and right of the ImageView that cannot go away.
This is done in java code, and is problematic:
Any help will be highly appreciated! Thanks in advance!
my_xml_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<ImageView
android:id="#+id/imageLeftArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:src="#drawable/dock_leftarrow" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</LinearLayout>
<ImageView
android:id="#+id/imageRightArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:src="#drawable/dock_rightarrow" />
</RelativeLayout>
</RelativeLayout>
I inflate it in the CustomView's Constructor through this line:
View.inflate( mContext, R.layout.my_xml_layout, this );
My CustomView's onLayout:
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Do nothing. Do not call the superclass method--that would start a layout pass
// on this view's children. PieChart lays out its children in onSizeChanged().
Log.e("DrawView", "DrawView.onLayout: " + l + ", " + t + ", " + r + ", " + b);
int iChildCount = this.getChildCount();
for ( int i = 0; i < iChildCount; i++ ) {
View pChild = this.getChildAt(i);
pChild.layout(0, 0, pChild.getMeasuredWidth(), pChild.getMeasuredHeight());
}
}
My CustomView's onMeasure:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Try for a width based on our minimum
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d("DockView", "DockView.onMeasure: width: " + widthMeasureSpec + " getWidth: " + MeasureSpec.getSize(widthMeasureSpec));
Log.d("DockView", "DockView.onMeasure: height: " + heightMeasureSpec + " getHeight: " + MeasureSpec.getSize(heightMeasureSpec));
Log.d("DockView", "DockView.onMeasure: getPaddingLeft: " + getPaddingLeft() + " getPaddingRight: " + getPaddingRight());
Log.d("DockView", "DockView.onMeasure: getPaddingTop: " + getPaddingTop() + " getPaddingBottom: " + getPaddingBottom());
// http://stackoverflow.com/a/17545273/474330
int iParentWidth = MeasureSpec.getSize(widthMeasureSpec);
int iParentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(iParentWidth, iParentHeight);
int iChildCount = this.getChildCount();
for ( int i = 0; i < iChildCount; i++ ) {
View pChild = this.getChildAt(i);
this.measureChild( pChild,
MeasureSpec.makeMeasureSpec(iParentWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(iParentHeight, MeasureSpec.EXACTLY)
);
}
}
For the time being, I am resorting to a hack.
I only add the LinearLayout as subview of my CustomView. Then I manually render the two ImageViews in onDraw(Canvas c); function of my CustomView. And in order to get the LinearLayout to fit into the remaining space between the two ImageViews, I calculate the margin of the LinearLayout in my CustomView's onLayout.
horizontal_dock_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/white" >
<ImageView
android:id="#+id/imageLauncher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_launcher"/>
</LinearLayout>
</RelativeLayout>
CustomView.java
/**
* Initialize the control. This code is in a separate method so that it can be
* called from both constructors.
*/
private void init() {
setWillNotDraw( false );
// Load the arrow bitmap
mArrowBitmap = ((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.dock_leftarrow)).getBitmap();
ViewGroup pRootView = (ViewGroup) View.inflate( mContext, R.layout.horizontal_dock_view, this );
Log.d("DockView", "DockView.init: " + pRootView.getClass().getCanonicalName());
mIconContainerView = (LinearLayout) ((RelativeLayout)pRootView.getChildAt(0)).getChildAt(0);
Log.d("DockView", "DockView.init: " + mIconContainerView.getClass().getCanonicalName());
// if ( mArrowBitmap != null ) {
// // set the icon container margin
// float fWidth = this.getWidth(); // View's width
// float fHeight = this.getHeight(); // View's height
// float fScale = fHeight / mArrowBitmap.getHeight();
// float fArrowWidth = mArrowBitmap.getWidth() * fScale;
// float fArrowHeight = mArrowBitmap.getHeight() * fScale;
// Log.d("DockView", "DockView.init: " + fArrowWidth + ", " + fArrowHeight );
// ((RelativeLayout.LayoutParams)mIconContainerView.getLayoutParams()).setMargins((int)fArrowWidth, 0, (int)fArrowWidth, 0);
// }
}
CustomView.onLayout:
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Do nothing. Do not call the superclass method--that would start a layout pass
// on this view's children. PieChart lays out its children in onSizeChanged().
Log.e("DrawView", "DrawView.onLayout: " + l + ", " + t + ", " + r + ", " + b);
if ( mIconContainerView != null && mArrowBitmap != null ) {
// set the icon container margin
float fHeight = this.getHeight();
float fScale = fHeight / mArrowBitmap.getHeight();
float fArrowWidth = mArrowBitmap.getWidth() * fScale;
float fArrowHeight = mArrowBitmap.getHeight() * fScale;
Log.d("DockView", "DockView.init: " + fArrowWidth + ", " + fArrowHeight );
((RelativeLayout.LayoutParams)mIconContainerView.getLayoutParams()).setMargins((int)fArrowWidth, 0, (int)fArrowWidth, 0);
this.requestLayout();
}
int iChildCount = this.getChildCount();
for ( int i = 0; i < iChildCount; i++ ) {
View pChild = this.getChildAt(i);
pChild.layout(0, 0, pChild.getMeasuredWidth(), pChild.getMeasuredHeight());
}
}
CustomView.onDraw
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// mBgColor = Color.CYAN;
// Log.e("DockView", "DockView.onDraw: " + mBgColor);
Log.e("DockView", "DockView.onDraw: width: " + this.getWidth() + " height: " + this.getHeight());
// debugChildren( (RelativeLayout) ((RelativeLayout)this.getChildAt(0)).getChildAt(0) );
debugChildren( ((RelativeLayout)this.getChildAt(0)) );
// draw the background
canvas.drawColor( mBgColor );
float fWidth = this.getWidth(); // View's width
float fHeight = this.getHeight(); // View's height
{
// draw the dock
float fTop = (2 * fHeight) / 3.0f;
Shader shader = new LinearGradient( 0, fTop, 0, fHeight, mDockTopGradientColor, mDockBottomGradientColor, TileMode.CLAMP );
Paint paint = new Paint();
paint.setShader(shader);
canvas.drawRect( new RectF( 0, fTop, fWidth, fHeight ), paint );
}
// moved to onLayout
// if ( mIconContainerView != null && mArrowBitmap != null ) {
// // set the icon container margin
// float fScale = fHeight / mArrowBitmap.getHeight();
// float fArrowWidth = mArrowBitmap.getWidth() * fScale;
// float fArrowHeight = mArrowBitmap.getHeight() * fScale;
// Log.d("DockView", "DockView.init: " + fArrowWidth + ", " + fArrowHeight );
// ((RelativeLayout.LayoutParams)mIconContainerView.getLayoutParams()).setMargins((int)fArrowWidth, 0, (int)fArrowWidth, 0);
// this.requestLayout();
// }
if ( mArrowBitmap != null ) {
// draw the arrow
// canvas.drawBitmap(mArrowBitmap, 0, 0, null);
float fScale = fHeight / mArrowBitmap.getHeight();
float fDrawnWidth = mArrowBitmap.getWidth() * fScale;
float fDrawnHeight = mArrowBitmap.getHeight() * fScale;
// float fLeft = fWidth - fDrawnWidth;
// float fTop = 0.0f;
// float fRight = fWidth;
// float fBottom = fDrawnHeight;
// Log.d("DockView", "DockView.onDraw: (" + fLeft + ", " + fTop + ", " + fRight + ", " + fBottom + ")");
canvas.drawBitmap(mArrowBitmap, null, new RectF(0, 0, fDrawnWidth, fDrawnHeight), null); // Left arrow
Log.d("DockView", "DockView.onDraw: (" + 0 + ", " + 0 + ", " + fDrawnWidth + ", " + fDrawnHeight + ")");
canvas.save();
canvas.scale(-1,1);
canvas.translate(-fWidth, 0);
// canvas.drawBitmap(mArrowBitmap, null, new RectF(fLeft, fTop, fRight, fBottom), null);
canvas.drawBitmap(mArrowBitmap, null, new RectF(0, 0, fDrawnWidth, fDrawnHeight), null); // Right arrow, flipped
canvas.restore();
}
}
Related
I have a game board with 5x5 squares made of canvas drawrect:
protected void onDraw(Canvas canvas) {
for (int rowNo = 0; rowNo < nSquares; rowNo++) {
paint.setColor(((rowNo & 1) == 0) ? colorA : colorB);
for (int colNo = 0; colNo < nSquares; colNo++) {
int left = colNo * squareWidth;
int top = rowNo * squareWidth;
Rect areaRect = new Rect(left, top, left + squareWidth, top + squareWidth);
canvas.drawRect(areaRect, paint);
paint.setColor((paint.getColor() == colorA) ? colorB : colorA);
paint.setTextSize((float)(squareWidth*0.8));
RectF bounds = new RectF(areaRect);
String letter = "A";
bounds.right = paint.measureText(letter, 0, letter.length());
bounds.bottom = paint.descent() - paint.ascent();
bounds.left += (areaRect.width() - bounds.right) / 2.0f;
bounds.top += (areaRect.height() - bounds.bottom) / 2.0f;
canvas.drawText(letter, bounds.left, bounds.top - paint.ascent(), paint);
}
}
I want to track touch input to get the squares the user are touching..
My attempt was
public boolean onTouchEvent(MotionEvent event){
int action = MotionEventCompat.getActionMasked(event);
int x = (int)event.getX();
int y = (int)event.getY();
Log.i(DEBUG_TAG, "Position: (" + x + ", " + y + ")");
int squareTouched = gameBoard.getSquare(x,y);
}
where getSquare is
public int getSquare(int x, int y) {
int row = 0;
int col = 0;
for(int rowNo = 1; rowNo <= nSquares; rowNo++) {
Log.i("Row", "Width: " + squareWidth + " rowNo: " + rowNo + " rowNo*squareW: " + rowNo*squareWidth + " y: " + y);
if(rowNo*squareWidth > y)
{
row = rowNo;
break;
}
}
for (int colNo = 1; colNo <= nSquares; colNo++) {
if(colNo*squareWidth > x)
{
col = colNo;
break;
}
}
Log.i(DEBUG_TAG, "Row: " + row + " Col: " + col);
return (row-1)*nSquares + col;
}
The problem is that the onTouchEvent getX and getY are referring to the screen 0,0, but when I draw the squares 0,0,0,0 is referring to the view origin? Am I right?
How can I get the input position relative to the game board view?
Could it be a solution to get the view position in the screen and add/subtract this to the tochEvent x- and y position?
I assume that the onDraw() and getSquare() belong to GameBoardView and onTouchEvent() to its immediate parent. If so then the onTouchEvent() should be like this
public boolean onTouchEvent(MotionEvent event){
int action = MotionEventCompat.getActionMasked(event);
int x = (int)event.getX() - gameBoard.getX();
int y = (int)event.getY() - gameBoard.getY();
Log.i(DEBUG_TAG, "Position: (" + x + ", " + y + ")");
int squareTouched = gameBoard.getSquare(x, y);
}
In a View x and y in both onDraw() and onTouchEvent() is relative to the View itself (Except when the View is scrolled). Since in your case onTouchEvent() belongs to parent and onDraw() to child, I used View.getX() and getY() to translate the coordinates. View.getX() returns the x position plus translationX of the View relative to its parent.
I could not get the gameBoard().getX/Y() to return the correct values. My guess is that the action bar is excluded?
I now use getLocationOnScreen
#Override
public boolean onTouchEvent(MotionEvent event){
int action = MotionEventCompat.getActionMasked(event);
int boardLocation[] = new int[2];
gameBoard.getLocationOnScreen(boardLocation);
int touchX = (int)event.getX();
int touchY = (int)event.getY();
int x = touchX - boardLocation[0];
int y = touchY - boardLocation[1];
int squareTouched = gameBoard.getSquare(x,y);
}
Which solved my issue..
Task: zoom layout to fill all available space.
I created container, called it ZoomViewGroup which measures single child inside itself and sets scaleX and scaleY depending on its own size.
package sample.andrew.myapplication;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import org.jetbrains.annotations.NotNull;
/**
* Container class for single child to be zoomed to fill container.
*/
public class ZoomViewGroup extends ViewGroup
{
private static final String log = "mopo-zoom";
#SuppressWarnings("UnusedDeclaration")
public ZoomViewGroup(Context context)
{
super(context);
}
#SuppressWarnings("UnusedDeclaration")
public ZoomViewGroup(Context context, AttributeSet attrs){
super(context, attrs);
}
#SuppressWarnings("UnusedDeclaration")
public ZoomViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private void checkCount() {
if(getChildCount() > 1)
{
throw new IllegalStateException("ZoomViewGroup can host only one direct child");
}
}
#Override
public void addView(#NotNull View child) {
checkCount();
super.addView(child);
}
#Override
public void addView(#NotNull View child, int index) {
checkCount();
super.addView(child, index);
}
#Override
public void addView(#NotNull View child, LayoutParams params) {
checkCount();
super.addView(child, params);
}
#Override
public void addView(#NotNull View child, int index, LayoutParams params) {
checkCount();
super.addView(child, index, params);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View child = getChildAt(0);
int fake_size = MeasureSpec.makeMeasureSpec(3000, MeasureSpec.UNSPECIFIED);
assert child != null;
assert child.getLayoutParams() != null;
if(child.getLayoutParams().width == LayoutParams.MATCH_PARENT)
{
int w = MeasureSpec.getSize(widthMeasureSpec);
int fake_w = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
// if(Log.ENABLED) Log.i(log, "measure child with width " + w + "x3000");
measureChildren(fake_w, fake_size);
// if(Log.ENABLED) Log.i(log, "measured child: " + child.getMeasuredWidth() + "x" + child.getMeasuredHeight());
}
else
{
// By using fake size we will get child measured with
// wrap_content size, so we can calculate needed zoom to fit
// child in whole space
measureChildren(fake_size, fake_size);
}
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
// ZoomViewGroup supports only match_parent layout params for itself,
// so we don't modify income sizes and set them directly.
setMeasuredDimension(w, h);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(Log.ENABLED) Log.i(log, "onLayout " + l + " " + t + " " + r + " " +b);
View child = getChildAt(0);
assert child != null;
if(child.getVisibility() != GONE) {
int w = r - l;
int h = b - t;
int child_w = child.getMeasuredWidth();
int child_h = child.getMeasuredHeight();
if(child_w == w && child_h == h)
{
child.layout(l, t, r, b);
}
else
{
int dx = w - child_w;
int dy = h - child_h;
if(Log.ENABLED) Log.i(log, "dx, dy " + dx + " " + dy);
int cl,ct,cr,cb;
if(dx == 0) {
cl = 0;
cr = child_w;
} else {
cl = (w-child_w)/2;
cr = cl + child_w;
}
if(dy == 0)
{
ct = 0;
cb = child_h;
}
else
{
ct = (h - child_h)/2;
cb = ct + child_h;
}
if(Log.ENABLED) Log.i(log, "set child bounds: " +
cl + " " + ct + " " + cr + " " + cb);
child.layout(cl, ct, cr, cb);
}
}
}
#Override
public void onSizeChanged(int width, int height, int oldw, int oldh) {
super.onSizeChanged(width, height, oldw, oldh);
if(Log.ENABLED) Log.i(log, "onSizeChanged " + oldw + "x" + oldh +
" -> " + width + "x" + height);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
View child = getChildAt(0);
if(child != null)
{
float originScaleX = child.getScaleX();
float originScaleY = child.getScaleY();
if(originScaleX != 1 || originScaleY != 1)
{
if(Log.ENABLED) Log.i(log, "scale set: " + originScaleX + " " + originScaleY);
if(Log.ENABLED) Log.i(log, "scale size: " + (int)((originScaleX*child.getMeasuredWidth())) + "x"
+ (int)(originScaleY*child.getMeasuredHeight())
+ " [" + child.getMeasuredWidth() + "x" + child.getMeasuredHeight() + "]" );
return;
}
#SuppressWarnings("ConstantConditions")
int originWidth = child.getMeasuredWidth();
int originHeight = child.getMeasuredHeight();
if(Log.ENABLED) Log.i(log, "child size: " + originWidth + " " + originHeight);
if(originWidth > 0)
{
float zoomFactorX = findZoomCoef(width, originWidth);
float zoomFactorY = findZoomCoef(height, originHeight);
if(Log.ENABLED) Log.i(log, "calc zoom [" + zoomFactorX + ", " + zoomFactorY + "]");
child.setScaleX(zoomFactorX);
child.setScaleY(zoomFactorY);
}
}
}
}
/**
* Calculates such coefficient to meet rule:
* size = (int)(sizeToZoom*coef)
* #param size
* #param sizeToZoom
* #return coef
*/
public static float findZoomCoef(int size, int sizeToZoom)
{
float zoomFactor = (size*100/sizeToZoom)/100.0f;
float step = 0.001f;
int count = 0;
do
{
int reverse_size = (int) (sizeToZoom*zoomFactor);
if(reverse_size == size)
break;
if(reverse_size < size)
{
zoomFactor += step;
}
else
{
zoomFactor -= step;
step /= 10;
}
count++;
}
while (true);
if(Log.ENABLED) Log.i(log, "calc zoom: s,c " + step + " " + count);
return zoomFactor;
}
}
This implementation works well for major count of devices except nexus 4 and nexus 5. This is first time I see this. Usually something doesn't work on samsung or sony devices but not vice versa.
Test layout to show problem:
<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"
tools:context=".MyActivity">
<sample.andrew.myapplication.ZoomViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:id="#+id/test_layout">
<View android:layout_width="200dp"
android:layout_height="200dp"
android:background="#70c0"
android:id="#+id/anchor"
/>
<View android:layout_width="20dp"
android:layout_height="20dp"
android:background="#f00"
android:layout_centerInParent="true"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/test_anchored_layout"
android:layout_alignTop="#id/anchor"
android:layout_alignLeft="#id/anchor"
android:layout_alignRight="#id/anchor"
android:layout_alignBottom="#id/anchor">
<!--<RelativeLayout-->
<!--android:layout_width="200dp"-->
<!--android:id="#+id/test_anchored_layout"-->
<!--android:layout_height="200dp">-->
<View android:layout_width="80dp"
android:layout_height="20dp"
android:layout_centerHorizontal="true"
android:background="#00f"
/>
</RelativeLayout>
</RelativeLayout>
</sample.andrew.myapplication.ZoomViewGroup>
</RelativeLayout>
How test sample looks:
1. android studio designer/preview
2 how it supposed to be and how it works for example on all galaxy devices
3 how it looks on nexus 4 and nexus 5
I found that if test_anchored_layout uses fixed size(commented part) and not layout rules then all is ok even on nexuses.
Bug of nexuses or I something don't understand?
Full project archive:
project zipped
apk file
Try
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/test_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" >
<View
android:id="#+id/anchor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#70c0" />
<View
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerInParent="true"
android:background="#f00" />
<RelativeLayout
android:id="#+id/test_anchored_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="#id/anchor"
android:layout_alignLeft="#id/anchor"
android:layout_alignRight="#id/anchor"
android:layout_alignTop="#id/anchor" >
<!-- <RelativeLayout -->
<!-- android:layout_width="200dp" -->
<!-- android:id="#+id/test_anchored_layout" -->
<!-- android:layout_height="200dp"> -->
<View
android:layout_width="80dp"
android:layout_height="20dp"
android:layout_centerHorizontal="true"
android:background="#00f" />
</RelativeLayout>
</RelativeLayout>
I have created a circle in custom view. I want to add 6 colors to it based on a condition. The 6 regions will keep changing based on the condition. For eg, each region can vary from 30 degrees to 90 degrees to 120 degrees.
My question is
1) How do I add 6 colors to the circle. Please note that I cannot divide the circle into 6 equal regions, thats not how it is supposed to be.
2) How do I assign the starting and ending points of the regions. For example if I want to add green color from 45degrees to 90 degrees. How do I do this?
The end product is supposed to look as below
package com.example.submission_customclock;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.Handler;
import android.text.format.Time;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews.RemoteView;
import java.util.TimeZone;
/**
* This widget display an analogic clock with two hands for hours and
* minutes.
*
* #attr ref android.R.styleable#AnalogClock_dial
* #attr ref android.R.styleable#AnalogClock_hand_hour
* #attr ref android.R.styleable#AnalogClock_hand_minute
*/
#RemoteView
public class AnalogClock extends View {
private Time mCalendar;
private static final String DEBUGTAG = "FA";
private Drawable mHourHand;
private Drawable mMinuteHand;
private Drawable mSecondHand;
private Drawable mDial;
private Drawable mDial_frame;
private Drawable mDial_center;
private int mDialWidth;
private int mDialHeight;
private boolean mAttached;
private final Handler mHandler = new Handler();
private float mMinutes;
private float mHour;
private boolean mChanged;
public AnalogClock(Context context) {
this(context, null);
}
public AnalogClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
Context mContext;
public AnalogClock(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
Resources r = context.getResources();
mContext=context;
Log.d(AnalogClock.DEBUGTAG,"Analog clock started");
mDial = r.getDrawable(R.drawable.clock4);
mDial_frame = r.getDrawable(R.drawable.clock_frame);
mDial_center = r.getDrawable(R.drawable.clock_dot);
mHourHand = r.getDrawable(R.drawable.hour_hand);
mMinuteHand = r.getDrawable(R.drawable.minute_hand);
mSecondHand = r.getDrawable(R.drawable.second_hand);
mCalendar = new Time();
mDialWidth = mDial.getIntrinsicWidth();
mDialHeight = mDial.getIntrinsicHeight();
}
#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);
}
// NOTE: It's safe to do these after registering the receiver since the receiver always runs
// in the main thread, therefore the receiver can't run before this method returns.
// The time zone may have changed while the receiver wasn't registered, so update the Time
mCalendar = new Time();
// Make sure we update to the current time
onTimeChanged();
counter.start();
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAttached) {
counter.cancel();
getContext().unregisterReceiver(mIntentReceiver);
mAttached = false;
}
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
float hScale = 1.0f;
float vScale = 1.0f;
if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {
hScale = (float) widthSize / (float) mDialWidth;
}
if (heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {
vScale = (float )heightSize / (float) mDialHeight;
}
float scale = Math.min(hScale, vScale);
Log.d(AnalogClock.DEBUGTAG,"onMeasure params: " + widthSize + " "
+ heightSize + " " + hScale + " " + vScale + " " + scale);
try {
setMeasuredDimension(resolveSizeAndState((int) (mDialWidth * scale), widthMeasureSpec, 0),
resolveSizeAndState((int) (mDialHeight * scale), heightMeasureSpec, 0));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mChanged = true;
}
boolean mSeconds=false;
float mSecond=0;
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
boolean changed = mChanged;
if (changed) {
mChanged = false;
}
boolean seconds = mSeconds;
if (seconds ) {
mSeconds = false;
}
int availableWidth = this.getMeasuredWidth();
int availableHeight = this.getMeasuredHeight();
int x = availableWidth / 2;
int y = availableHeight / 2;
final Drawable dial = mDial;
final Drawable dial_frame = mDial_frame;
final Drawable dial_dot = mDial_center;
int w = dial.getIntrinsicWidth();
int h = dial.getIntrinsicHeight();
boolean scaled = false;
// Log.d(AnalogClock.DEBUGTAG,"onDraw params: " + availableWidth +" "+ availableHeight + " " +
// x + " " + y + " " + w + " "+ h + " " + changed);
if (availableWidth < w || availableHeight < h) {
scaled = true;
//float scale = Math.min((float) availableWidth / (float) w,
// (float) availableHeight / (float) h);
canvas.save();
float scale1 = (float) 0.6;
float scale2 = (float) 0.8;
// Log.d(AnalogClock.DEBUGTAG,"scale params: " + scale1 + " " + scale2);
canvas.scale(scale1, scale2, x, y);
}
if (changed) {
//Log.d(AnalogClock.DEBUGTAG,"Bounds params: " + (x - (w / 2)) + " " + (y - (h / 2)) + " " + ( x + (w / 2)) + " " + (y + (h / 2)));
dial.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
//dial_frame.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
//Log.d(AnalogClock.DEBUGTAG,"Bounds params: " + (x - (w / 2 + w/10)) + " " + (y - (h / 2 + h/10)) + " " + ( x + (w / 2 + w/10)) + " " +
// (y + (h / 2 + h/10)));
dial_frame.setBounds(x - (w/2 + w/10), y - (h/2 + h/10), x + (w/2 + w/10), y + (h/2 + h/10));
dial_dot.setBounds(x -30 , y -20 , x + 30, y + 20);
//Log.d(AnalogClock.DEBUGTAG,"Bounds params: " + (x ) + " " + (y) + " " + ( x + (w / 2 )) + " " +
// (y + (h / 2)));
}
int radius = 0;
if(x>y)
radius=y-10;
else
radius=x-10;
Paint circlepaint;
circlepaint = new Paint();
circlepaint.
dial.draw(canvas);
dial_frame.draw(canvas);
// canvas.drawCircle(x, y, radius, circlepaint);
canvas.save();
canvas.rotate(mHour / 12.0f * 180.0f, x - 10, y - 10);
final Drawable hourHand = mHourHand;
if (changed) {
w = hourHand.getIntrinsicWidth();
h = hourHand.getIntrinsicHeight();
hourHand.setBounds(x -w/2, y - h/2 - h/4 , x + w/6, y + h/6);
}
hourHand.draw(canvas);
canvas.restore();
canvas.save();
canvas.rotate(mMinutes / 60.0f * 360.0f, x - 10, y - 10);
final Drawable minuteHand = mMinuteHand;
if (changed) {
w = minuteHand.getIntrinsicWidth();
h = minuteHand.getIntrinsicHeight();
//minuteHand.setBounds(x, y, x + (w / 2 + w/10), y + (h / 2 + w/10));
minuteHand.setBounds(x - w, y - h/2, x + w/6, y + h/6);
// Log.d(AnalogClock.DEBUGTAG,"Bounds params:x " + (x) + " y " + (y) + " w " + ( w ) + " h " +
// (h));
// Log.d(AnalogClock.DEBUGTAG,"Bounds params: " + (x - w) + " " + (y - h/2) + " " + ( x ) + " " +
// (y));
}
minuteHand.draw(canvas);
canvas.restore();
canvas.save();
canvas.rotate(mSecond, x, y);
if (seconds) {
w = mSecondHand.getIntrinsicWidth();
h = mSecondHand.getIntrinsicHeight();
// mSecondHand.setBounds(x, y, x + (w / 2 + w/10), y + (h / 2 + w/10));
mSecondHand.setBounds(x-w/6, y-h/6, x + w,y + h/2);
Log.d(AnalogClock.DEBUGTAG,"Bounds params: " + (x ) + " " + (y) + " " + ( w) + " " +
(h));
}
mSecondHand.draw(canvas);
canvas.restore();
canvas.save();
dial_dot.draw(canvas);
if (scaled) {
canvas.restore();
}
}
MyCount counter = new MyCount(10000, 1000);
public class MyCount extends CountDownTimer{
public MyCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
#Override
public void onFinish() {
counter.start();
}
#Override
public void onTick(long millisUntilFinished) {
mCalendar.setToNow();
int second = mCalendar.second;
mSecond=6.0f*second;
mSeconds=true;
//mChanged = true;
AnalogClock.this.invalidate();
}
}
private void onTimeChanged() {
mCalendar.setToNow();
int hour = mCalendar.hour;
int minute = mCalendar.minute;
int second = mCalendar.second;
mMinutes = minute + second / 60.0f;
mHour = hour + mMinutes / 60.0f;
mChanged = true;
}
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
String tz = intent.getStringExtra("time-zone");
mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
}
onTimeChanged();
invalidate();
}
};
}
i am trying to send data in a listview when a button is clicked.
However my listview show 2 row on at once one full row and one partial row . Is there a way i can determine which row is showing partial and which is showing fully.
I am able to get the index that is showing only. is there another approach ?
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE){
Rect r = new Rect ();
View child = recordListview.getChildAt(view.getFirstVisiblePosition()); // first visible child
if (child == null)
return;
double height = child.getHeight () * 1.0;
recordListview.getChildVisibleRect (child, r, null);
Log.d("Visible1 ", view.getFirstVisiblePosition() + " " + height + " " + r.height() );
if (Math.abs (r.height ()) < height / 2.0) {
// show next child
recordListview.smoothScrollToPosition(view.getFirstVisiblePosition()+1);
Log.d("Visible1 Location", view.getFirstVisiblePosition() +1+ "");
}
else {
recordListview.smoothScrollToPosition(view.getFirstVisiblePosition());
Log.d("Visible1 Location", view.getFirstVisiblePosition()+ "");
}
}
}
});
Seems You've understood documentation of getChildVisibleRect() incorrectly.
It mentions:
r The input rectangle, defined in the child coordinate system. Will
be overwritten to contain the resulting visible rectangle, expressed
in global (root) coordinates
So, if You're providing empty rectangle in the child coordinate then it can be translated only into empty visible rectagle, right?
For me this code seems to work:
recordListview.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
final View child = recordListview.getChildAt(view.getFirstVisiblePosition());
if (child == null) {
return;
}
final Rect r = new Rect (0, 0, child.getWidth(), child.getHeight());
final double height = child.getHeight () * 1.0;
recordListview.getChildVisibleRect(child, r, null);
Log.d("Visible1 ", view.getFirstVisiblePosition() + " " + height + " " + r.height());
if (Math.abs (r.height ()) < height / 2.0) {
// show next child
recordListview.smoothScrollToPosition(view.getFirstVisiblePosition()+1);
Log.d("Visible1 Location", view.getFirstVisiblePosition() +1+ "");
} else {
recordListview.smoothScrollToPosition(view.getFirstVisiblePosition());
Log.d("Visible1 Location", view.getFirstVisiblePosition()+ "");
}
}
}
#Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {
// nothing to do here
}
});
Regarding initial question about determining which view is visible fully and which is not, I would suggest to use the following code:
#Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
final int firstVisiblePosition = view.getFirstVisiblePosition();
View child = recordListview.getChildAt(firstVisiblePosition);
if (child == null) {
return;
}
if (mListItemsOnScreen == 0) {
// number of total visible items, including items which are not fully visible
mListItemsOnScreen = (int) Math.ceil(((double)recordListview.getHeight()) / (child.getHeight() + recordListview.getDividerHeight()));
}
final Rect r = new Rect(0, 0, child.getWidth(), child.getHeight());
final double height = child.getHeight();
recordListview.getChildVisibleRect(child, r, null);
Log.d("Visible1", " items till " + firstVisiblePosition + " are not visible");
// Check top item
Log.d("Visible1", firstVisiblePosition + " is visible " + (r.height() >= height ? " fully" : "partially"));
// check bottom item
child = recordListview.getChildAt(firstVisiblePosition + mListItemsOnScreen);
if (child != null) {
r.set(0, 0, child.getWidth(), child.getHeight());
recordListview.getChildVisibleRect(child, r, null);
Log.d("Visible1", " items from " + firstVisiblePosition + " till " + (firstVisiblePosition + mListItemsOnScreen) + " are fully visible");
Log.d("Visible1", (firstVisiblePosition + mListItemsOnScreen) + " is visible " + (r.height() >= height ? " fully" : "partially"));
} else {
Log.d("Visible1", " items from " + firstVisiblePosition + " till " + (firstVisiblePosition + mListItemsOnScreen) + " are fully visible");
Log.d("Visible1", (firstVisiblePosition + mListItemsOnScreen) + " is invisible ");
}
}
}
I created a custom object that has a Bitmap field. I'm drawing several of these objects to the canvas of a View to mimic a scrolling horizontal image gallery.
When the user long presses one of the images, I want to change the opacity of the remaining Bitmaps to a specified percentage. This could give the impression that they've darkened for "edit mode", or it could mean they've returned to normal. (Please note that I don't want to permanently alter the Bitmaps. I want to be able to adjust their opacity on the fly.)
I pieced together the following code from various forums, and everything seems to be working except for the change in opacity. I've confirmed that my Bitmaps are mutable and have alpha every step of the way. What am I doing wrong?
Targeting Android 2.1, API Level 7
View (abridged for brevity):
public class CanvasStoryEdit2 extends View
{
public CanvasStoryEdit2(Context context, AttributeSet attrs) {
super(context, attrs);
for (int i = 0; i < getResources().getInteger(R.integer.maxAllowedSlides); i++)
{
ImageStoryEdit img = new ImageStoryEdit();
//test images
if (i == 0) { resource = R.drawable.a1; }
else if (i == 1) { resource = R.drawable.a2; }
else if (i == 2) { resource = R.drawable.a3; }
else if (i == 3) { resource = R.drawable.a4; }
else if (i == 4) { resource = R.drawable.a5; }
Bitmap bmp = BitmapFactory.decodeResource(getResources(), resource);
Log.d("TEST", "[" + Integer.toString(i) + "] - config: " + bmp.getConfig().toString());
Log.d("TEST", "[" + Integer.toString(i) + "] - hasAlpha: " + Boolean.toString(bmp.hasAlpha()));
Log.d("TEST", "[" + Integer.toString(i) + "] - isMutable: " + Boolean.toString(bmp.isMutable()));
final int imgHeight = bmp.getHeight() / (bmp.getWidth() / imgWidth);
bmp = Bitmap.createScaledBitmap(bmp, imgWidth, imgHeight, false);
Log.d("TEST", "[" + Integer.toString(i) + "] - config: " + bmp.getConfig().toString());
Log.d("TEST", "[" + Integer.toString(i) + "] - hasAlpha: " + Boolean.toString(bmp.hasAlpha()));
Log.d("TEST", "[" + Integer.toString(i) + "] - isMutable: " + Boolean.toString(bmp.isMutable()));
int width = bmp.getWidth();
int height = bmp.getHeight();
int[] pixels = new int[width * height];
bmp.getPixels(pixels, 0, width, 0, 0, width, height);
bmp.recycle();
bmp = null;
img.setBmp(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888));
img.getBmp().setPixels(pixels, 0, width, 0, 0, width, height);
pixels = null;
Log.d("TEST", "[" + Integer.toString(i) + "] - config: " + img.getBmp().getConfig().toString());
Log.d("TEST", "[" + Integer.toString(i) + "] - hasAlpha: " + Boolean.toString(img.getBmp().hasAlpha()));
Log.d("TEST", "[" + Integer.toString(i) + "] - isMutable: " + Boolean.toString(img.getBmp().isMutable()));
imageStoryEditList.add(img);
}
}
Call made on long press:
{
img.setOpacity(50);
invalidate();
}
ImageStoryEdit (also abridged):
public class ImageStoryEdit
{
private int opacity;
public Bitmap bmp;
public Bitmap getBmp() {
return bmp;
}
public void setBmp(Bitmap bmp)
{
this.bmp = bmp;
UpdateRectF();
}
public int getOpacity()
{
return opacity;
}
public void setOpacity(int percent)
{
//ADJUST FOR RANGE OF 0-100%
percent = percent < 0 ? 0 : percent;
percent = percent > 100 ? 100 : percent;
this.opacity = percent;
int opacity = (int) (this.opacity * 2.55);
Log.d("TEST", "OPACITY = " + Integer.toString(percent) + " : " + Integer.toString(opacity));
adjustOpacity(opacity);
}
private void adjustOpacity(int opacity)
{
Log.d("TEST", "OPACITY = " + Integer.toString(opacity));
Log.d("TEST", this.bmp.getConfig().toString());
Log.d("TEST", "hasAlpha: " + Boolean.toString(this.bmp.hasAlpha()));
Log.d("TEST", "isMutable: " + Boolean.toString(this.bmp.isMutable()));
Bitmap bmp2 = this.bmp.copy(Config.ARGB_8888, true);
Canvas canvas = new Canvas(bmp2);
Paint paint = new Paint();
paint.setAlpha(opacity);
canvas.drawBitmap(bmp2, 0, 0, paint);
this.bmp = bmp2;
Log.d("TEST", this.bmp.getConfig().toString());
Log.d("TEST", "hasAlpha: " + Boolean.toString(this.bmp.hasAlpha()));
Log.d("TEST", "isMutable: " + Boolean.toString(this.bmp.isMutable()));
Log.d("TEST", "DONE");
}
}
I think I figured this one out on my own, but I welcome any feedback if anyone can offer additional insight.
I was able to do what I wanted by using a BitmapDrawable. It's a wrapper for a Bitmap. Editing the Bitmap itself is, indeed, permanent. BitmapDrawables allow you to change certain parameters without directly affecting the underlying Bitmap.