Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a 3D circular scrolling view for Text? (As seen in Appy Geek)

Tags:

android

I am looking to implement some sort of "canvas" where you can place X number of TextViews/Links at "random positions" (Positioned like in the image below). You would then be able to scroll this "canvas" view left or right continuously and the view will repeat/be circular (sort of like a HTML marquee except that you are doing the scrolling manually). In the most simplest of cases I am just looking to have horizontal scrolling - but an example of a more "complex case" is where you can do "sphere scrolling" - see the example below from Appy Geek. (For now I am just interested in the horizontal scrolling)

Example from Appy Geek:

enter image description here

like image 701
user3034 Avatar asked Nov 25 '13 12:11

user3034


1 Answers

Well this will get you started, I have implemented a simple tag cloud using both approaches (i.e. by extending View and ViewGroup) that keeps rotating. You can use this logic in your custom ViewGroup which positions its View's accordingly. After that add clickable TextViews inside that layout and handle touch events.

Final result (ofcourse its rotating, look closer):

enter image description here

Lot of things can be improved in the following code.

BY EXTENDING ViewGroup:

Put this in xml layout:

 <com.vj.tagcloud.TagCloudLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" /> 
</com.vj.tagcloud.TagCloudLayout>

TagCloudLayout class:

import java.util.Random;

import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class TagCloudLayout extends ViewGroup {
final Random mRandom = new Random();
private float mRotateAngle;

private Handler mHandler = new Handler();
private float rotateAngleDegree;

public TagCloudLayout(Context context) {
    super(context);
}

public TagCloudLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public TagCloudLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    final float radius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2F;
    float halfWidth = getMeasuredWidth() / 2F;
    float halfHeight = getMeasuredHeight() / 2F;
    final int count = getChildCount();
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        LayoutParams lp = (LayoutParams) child.getLayoutParams();
        float sinTheta = (float) Math.sin(lp.theta);
        float x = (int) (radius * Math.cos(lp.fi + mRotateAngle)
                * sinTheta);

        if (child instanceof TextView) {
            ((TextView) child)
                    .setTextSize(15 * ((radius - x) / radius) + 10);
        }
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
        //  http://en.wikipedia.org/wiki/Spherical_coordinates
        lp.x = (int) ((halfWidth + radius * Math.sin(lp.fi + mRotateAngle)
                * sinTheta) - /* for balancing on x-axis */(child
                .getMeasuredWidth() / 2F));
        lp.y = (int) (halfHeight + radius * Math.cos(lp.theta)-/* for balancing on y-axis */(child
                .getMeasuredHeight() / 2F));
    }
}

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    mHandler.postDelayed(new Runnable() {

        @Override
        public void run() {
            rotateAngleDegree += 5;
            mRotateAngle = (float) Math.toRadians(rotateAngleDegree);
            requestLayout();
            mHandler.postDelayed(this, 40);
        }
    }, 40);
}

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    mHandler.removeCallbacksAndMessages(null);
}

@Override
public void addView(View child, int index,
        android.view.ViewGroup.LayoutParams params) {
    super.addView(child, index, params);

    LayoutParams lp = (LayoutParams) child.getLayoutParams();
    lp.fi = (float) Math.toRadians(mRandom.nextInt(360));
    lp.theta = (float) Math.toRadians(mRandom.nextInt(360));
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = getChildCount();
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        LayoutParams lp = (LayoutParams) child.getLayoutParams();
        child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y
                + child.getMeasuredHeight());
    }
}

@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof LayoutParams;
}

@Override
protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.WRAP_CONTENT,
            LayoutParams.WRAP_CONTENT);
}

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
}

@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    return new LayoutParams(p.width, p.height);
}

public static class LayoutParams extends ViewGroup.LayoutParams {
    int x;
    int y;
    float fi, theta;

    public LayoutParams(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public LayoutParams(int w, int h) {
        super(w, h);
    }
}
}

BY EXTENDING View:

Put this in xml layout:

<com.vj.wordtap.TagCloud 
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

and this in java code:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import android.content.Context;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;

public class TagCloud extends View {

private List<String> mItems = new ArrayList<String>();
private List<Angles> mAngles = new ArrayList<Angles>();
private Camera mCamera = new Camera();
private TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
private Handler mHandler = new Handler();
private float mRotateAngle;
private float rotateAngleDegree;

public static class Angles {
    float fi, theta;
}

public TagCloud(Context context) {
    super(context);
    init();
}

public TagCloud(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public TagCloud(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init() {
    List<String> items = new ArrayList<String>();
    for (int i = 0; i < 10; i++) {
        items.add("item:" + i);
    }
    setItems(items);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(canvas.getWidth() / 2F, canvas.getHeight() / 2F);
    mTextPaint.setColor(Color.BLACK);
    final float radius = 100;
    mCamera.setLocation(0, 0, -100);
    for (int i = 0; i < mItems.size(); i++) {
        String item = mItems.get(i);
        Angles xyz = mAngles.get(i);
        mCamera.save();
        canvas.save();
        float sinTheta = (float) Math.sin(xyz.theta);
        float x = (float) (radius * Math.cos(xyz.fi + mRotateAngle) * sinTheta);
        float y = (float) (radius * Math.sin(xyz.fi + mRotateAngle) * sinTheta);
        float z = (float) (radius * Math.cos(xyz.theta));
        // mapping coordinates with Android's coordinates
        // http://en.wikipedia.org/wiki/Spherical_coordinates
        mCamera.translate(y, z, x);
        mCamera.applyToCanvas(canvas);

        // http://en.wikipedia.org/wiki/Spherical_coordinates
        // set size based on x-Axis that is coming towards us
        mTextPaint.setTextSize(20 * ((100 - x) / 100) + 10);
        canvas.drawText(item, 0, 0, mTextPaint);
        mCamera.restore();
        canvas.restore();
    }
}

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    mHandler.postDelayed(new Runnable() {

        @Override
        public void run() {
            rotateAngleDegree += 5;
            mRotateAngle = (float) Math.toRadians(rotateAngleDegree);
            invalidate();
            mHandler.postDelayed(this, 40);
        }
    }, 40);
}

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    mHandler.removeCallbacksAndMessages(null);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

public void setItems(List<String> items) {
    mItems = items;
    final Random ran = new Random();
    final List<Angles> xyzList = mAngles;
    xyzList.clear();

    for (int i = 0; i < items.size(); i++) {
        Angles xyz = new Angles();
        float fi = (float) Math.toRadians(ran.nextInt(360));
        xyz.fi = fi;
        float theta = (float) Math.toRadians(ran.nextInt(360));
        xyz.theta = theta;
        xyzList.add(xyz);
    }
}
}
like image 100
M-WaJeEh Avatar answered Nov 15 '22 13:11

M-WaJeEh