I've been trying to get an app idea of mine to come to fruition for some time now. The idea is a weather app in which users can choose the content they want to see, and it is represented by cards (CardViews). For example, if they want to see only current weather and a 3 day forecast, they can enable that, and they'll see only those two cards in the MainActivity in a Google Now sort of fashion.
I've run into a few problems, and I need some help.
Firstly, how can I create the CardViews dynamically? My first attempt had the views completely defined in XML, but that didn't work very well because it doesn't seem very intuitive to hide/show them all the time. So I decided to extend the CardView class into things like a CurrentWeatherCardView and create the card completely programatically.
Here's the class I created from that. It's a child of the WeatherCardView class, which just extends CardView and doesn't do a whole lot else at this point.
package com.photonfighterlabs.particleweather.weathercardviews;
import android.app.Activity;
import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.photonfighterlabs.particleweather.MainActivity;
import com.photonfighterlabs.particleweather.weatherobjects.CurrentWeather;
import com.photonfighterlabs.particleweather.weatherobjects.Weather;
import java.io.File;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
public class CurrentWeatherCardView extends WeatherCardView {
private ViewGroup viewGroup;
private LinearLayout linearLayout_600, linearLayout_604, linearLayout_434;
private TextView cw_title, temp_text_view;
private ImageView icon_image_view;
private int is_day;
private CurrentWeather currentWeather;
public CurrentWeatherCardView(CurrentWeather currentWeather, Context context, Activity activity, ViewGroup viewGroup) {
super(context, activity);
this.currentWeather = currentWeather;
this.currentWeather.initialize(context, activity, MainActivity.API_KEY);
this.context = currentWeather.getContext();
this.viewGroup = viewGroup;
this.currentWeather.doOnResponse(() -> {
setupLayout();
setCw_title(currentWeather.getName(), currentWeather.getText());
Weather.setIconDrawable(currentWeather.getIcon(), icon_image_view, currentWeather.getIs_day());
setTemperature(currentWeather.getTemp_f());
});
}
public CurrentWeatherCardView(Context context) {
super(context);
}
public CurrentWeatherCardView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CurrentWeatherCardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void setupLayout() {
LayoutParams cardParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, pixels(200));
cardParams.setMargins(
pixels(15),
pixels(15),
pixels(15),
0
);
super.setLayoutParams(cardParams);
super.setRadius(pixels(4));
super.setUseCompatPadding(true);
linearLayout_604 = new LinearLayout(context);
linearLayout_604.setBaselineAligned(false);
linearLayout_604.setOrientation(VERTICAL);
LayoutParams layout_170 = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
linearLayout_604.setLayoutParams(layout_170);
this.addView(linearLayout_604);
linearLayout_600 = new LinearLayout(context);
linearLayout_600.setOrientation(LinearLayout.HORIZONTAL);
LayoutParams layout_676 = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, pixels(50));
linearLayout_600.setLayoutParams(layout_676);
linearLayout_604.addView(linearLayout_600);
cw_title = new TextView(context);
cw_title.setTypeface(Typeface.SANS_SERIF);
cw_title.setTextSize(pixels(5));
LayoutParams layout_914 = new LayoutParams(pixels(250), pixels(25));
layout_914.setMarginStart(pixels(10));
layout_914.topMargin = pixels(10);
cw_title.setLayoutParams(layout_914);
linearLayout_600.addView(cw_title);
icon_image_view = new ImageView(context);
LayoutParams layout_501 = new LayoutParams(pixels(75), pixels(40));
layout_501.setMarginStart(pixels(45));
layout_501.topMargin = pixels(10);
icon_image_view.setLayoutParams(layout_501);
linearLayout_600.addView(icon_image_view);
linearLayout_434 = new LinearLayout(context);
linearLayout_434.setOrientation(HORIZONTAL);
LayoutParams layout_204 = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
linearLayout_434.setLayoutParams(layout_204);
temp_text_view = new TextView(context);
temp_text_view.setTextAlignment(TEXT_ALIGNMENT_TEXT_START);
temp_text_view.setTextSize(pixels(8));
LayoutParams layout_725 = new LayoutParams(pixels(100), pixels(100));
layout_725.setMarginStart(pixels(25));
layout_725.topMargin = pixels(10);
temp_text_view.setLayoutParams(layout_725);
linearLayout_434.addView(temp_text_view);
linearLayout_604.addView(linearLayout_434);
viewGroup.addView(this);
}
private void setCw_title(String title, String condition) {
cw_title.setText(title + " - " + condition);
}
private void setTemperature(double temp) {
temp_text_view.setText(String.valueOf((int) temp) + '\u00b0');
}
}
It's messy, but is there a better way? Doing it this way allows me to create this CardView by just instantiating the class, but I'm running into issues with figuring out what kind of view I should put these in. RecyclerView doesn't really seem like the right choice because as far as I can tell it's creates cards from a dataset. In my case, the CurrentWeatherCardView will always be the same.
That leads me to my next question. In what way can I have a scrollable, side-swipeable set of cards? RecyclerView? ListView? I'm pretty lost, and any guidance would help.
CardView is extended from FrameLayout, so you can just use a xml to design what your card looks like, and use findViewById in this CurrentWeatherCardView. For example:
public CurrentWeatherCardView(Context context) {
super(context);
init();
}
public CurrentWeatherCardView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CurrentWeatherCardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
LayoutInflater.from(this).inflate(R.layout.xxx, this, true);
xxx = findViewById(R.id.xxxxx);
...
xxx.setText(xxxx)
// if you want to change visibility, just call:
setVisibility(View.GONE/View.VISIBILE);
}
Related
This question already has answers here:
Error inflating custom view
(5 answers)
Closed 2 years ago.
I am trying to do a photo editor app for my own learning and I am struggling to incorporate a View subclass in an XML file to better design the process.
Here is the XML code :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".home">
<com.example.digitaldesign.createObjectLayout
android:id="#+id/CreateObj"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Here is the View Class:
package com.example.digitaldesign;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.View;
public class createObjectLayout extends View {
Bitmap tshirt;
public createObjectLayout(Context context) {
super(context);
setBackgroundResource(R.drawable.wall);
tshirt = BitmapFactory.decodeResource(getResources(),R.drawable.tshirt);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect trect = new Rect();
trect.set(50,50,1050,1350 );
canvas.drawBitmap(tshirt,null,trect,null);
}
}
And here is the Activity:
```package com.example.digitaldesign;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.LinearLayout;
public class createObject extends AppCompatActivity {
createObjectLayout CreateObj;
Button btn;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CreateObj = new createObjectLayout(this);
LinearLayout layout1 =(LinearLayout) findViewById(R.id.CreateObj);
layout1.addView(CreateObj);
setContentView(R.layout.createobjectlyt);
}
}
And the error:
Caused by: android.view.InflateException: Binary XML file line #10: Binary
XML file line #10: Error inflating class
com.example.digitaldesign.createObjectLayout
Caused by: android.view.InflateException: Binary XML file line #10:
Error inflating class com.example.digitaldesign.createObjectLayout
Caused by: java.lang.NoSuchMethodException: <init> [class
android.content.Context, interface android.util.AttributeSet]
I tried to do everything like here : https://stackoverflow.com/questions/16504243/add-a-button-to-view#= , but i still get the error. Can you please tell me what am i doing wrong and how to fix it?
First of all you must get an error as you try to inflate widgets from XML layout before using setContentView()
So, modify your code to transfer setContentView() just after super.onCreate(savedInstanceState);
public class createObject extends AppCompatActivity {
createObjectLayout CreateObj;
Button btn;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.createobjectlyt);
CreateObj = new createObjectLayout(this);
...
}
}
The second thing you need to include all the constructors of your custom view
public class createObjectLayout extends View {
Bitmap tshirt;
public createObjectLayout(Context context) {
super(context);
setBackgroundResource(R.drawable.ic_launcher_background);
tshirt = BitmapFactory.decodeResource(getResources(), R.drawable.ic_menu_gallery);
}
public createObjectLayout(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
setBackgroundResource(R.drawable.ic_launcher_background);
tshirt = BitmapFactory.decodeResource(getResources(), R.drawable.ic_menu_gallery);
}
public createObjectLayout(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setBackgroundResource(R.drawable.ic_launcher_background);
tshirt = BitmapFactory.decodeResource(getResources(), R.drawable.ic_menu_gallery);
}
public createObjectLayout(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setBackgroundResource(R.drawable.ic_launcher_background);
tshirt = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect trect = new Rect();
trect.set(50, 50, 1050, 1350);
canvas.drawBitmap(tshirt, trect, trect, new Paint());
}
}
Third, you will get ClassCastException as you inflate your custom view as a LinearLayout so change it to
createObjectLayout myView = findViewById(R.id.CreateObj);
Also as your custom view is not a ViewGroup, then you can't add views to it. So the below statement will raise exception
layout1.addView(CreateObj);
I encourage your to have a look at Android Custom Views
This question already has answers here:
How to set a particular font for a button text in android?
(9 answers)
Closed 5 years ago.
I created an android studio default navgation view and i put two buttons in nav_header_main.xml (see the picture below)
now i want to set a custom font for the button.
i tried this in MainActivity.java :
Typeface font = Typeface.createFromAsset(getAssets(), "fonts/myfont.ttf");
Button b = (Button) findViewById(R.id.login_btn);
b.setTypeface(font);
but it didn't work!!
how can i do this?
Screenshot
You have to write a custom class extending from Button.
Here is an example:
package com.example.test;
import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.widget.Button;
import android.widget.TextView;
public class CustomFontButton extends Button {
public CustomFontButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public CustomFontButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomFontButton(Context context) {
super(context);
init();
}
private void init() {
Typeface font = Typeface.createFromAsset(getContext().getAssets(), "fonts/myfont.ttf");
setTypeface(font);
}
}
Use it from XML:
<com.example.test.CustomFontButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Button Text" />
I am trying to change the default font in my app. But its not working. These are steps I have taken:
1) Created class TypefaceUtil.java
import android.content.Context;
import android.graphics.Typeface;
import android.util.Log;
import java.lang.reflect.Field;
public class TypefaceUtil {
public static void overrideFont(Context context, String defaultFontNameToOverride, String customFontFileNameInAssets) {
try {
final Typeface customFontTypeface = Typeface.createFromAsset(context.getAssets(), customFontFileNameInAssets);
final Field defaultFontTypefaceField = Typeface.class.getDeclaredField(defaultFontNameToOverride);
defaultFontTypefaceField.setAccessible(true);
defaultFontTypefaceField.set(null, customFontTypeface);
} catch (Exception e) {
Log.e("CustomFontException", "Can not set custom font " + customFontFileNameInAssets + " instead of " + defaultFontNameToOverride);
}
}
}
2) In a class extending Application:
public void onCreate() {
super.onCreate();
TypefaceUtil.overrideFont(getApplicationContext(), "MONOSPACE", "fonts/varelaround_regular.ttf");
}
3) In styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:typeface">monospace</item>
</style>
Still its not working. Am I missing something ?
I had faced this problem once. I am not very sure why it works, but you can try the following :
Instead of "monospace", try each of these:
DEFAULT, SANS_SERIF, SERIF. It might not work for all textviews, like those in ListView or recyclerView (weird, right?). But in those cases, I set the typeface programatically from the adapter.
Sorry for unable to explain the reason.
For this purpose, i highly recommend you to use Calligraphy, https://github.com/chrisjenx/Calligraphy, an awesome lib, really strait-forward to change the default font with it, and has many other useful functionalities.
Everything you need to set this up should be in the Readme.
Why not define your custom TextView and assign any font to it.
import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.widget.EditText;
import com.exmple.util.Utility;
public class CustomFontEditText extends EditText {
private Context mContext;
private String ttfName;
String TAG = getClass().getName();
public CustomFontEditText(Context context) {
super(context);
this.mContext = context;
}
public CustomFontEditText(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
// Typeface.createFromAsset doesn't work in the layout editor.
// Skipping...
if (isInEditMode()) {
return;
}
for (int i = 0; i < attrs.getAttributeCount(); i++) {
this.ttfName = attrs.getAttributeValue(Utility.ATTRIBUTE_SCHEMA,
Utility.ATTRIBUTE_TTF_KEY);
if (null != ttfName)
init();
}
}
public CustomFontEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.mContext = context;
}
private void init() {
Typeface font = Utility.getFonts(mContext, ttfName);
if (null != font)
setTypeface(font);
}
#Override
public void setTypeface(Typeface tf) {
super.setTypeface(tf);
}
}
Load and store font from asset in a hash map in your Utility class so that you can reuse font later. Imagine loading font each time new View get loaded .. OOM ..!!!
private static Map<String, Typeface> TYPEFACE = new HashMap<String, Typeface>();
public static Typeface getFonts(Context context, String fontName) {
Typeface typeface = TYPEFACE.get(fontName);
if (typeface == null) {
typeface = Typeface.createFromAsset(context.getAssets(), "fonts/"+fontName);
TYPEFACE.put(fontName, typeface);
}
return typeface;
}
Add ttf attribute to set font from xml
public static final String ATTRIBUTE_TTF_KEY = "ttf_name";
public static final String ATTRIBUTE_SCHEMA = "http://schemas.android.com/apk/lib/com.exmaple.ui.customfont";
and now use in your layout file
<com.example.ui.customviews.CustomFontEditText
xmlns:font="http://schemas.android.com/apk/lib/com.exmaple.ui.customfont"
android:id="#+id/edt_pay_input"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:background="#null"
android:cursorVisible="false"
android:drawableBottom="#drawable/dashed_white"
android:includeFontPadding="false"
android:inputType="numberDecimal"
android:maxLength="4"
android:minWidth="20dp"
android:singleLine="true"
android:textColor="#color/white"
android:textSize="30sp"
font:ttf_name="FedraSansStd-Light.otf" >
<requestFocus />
</com.example.ui.customviews.CustomFontEditText>
You can do the same for Button,TextView etc.
I have an ImageView, which will fill the entire width of the screen and will also be animated up and down like a wave at the bottom of the screen. I have used scaleType="matrix" since it should not stretch to fit within the screen.
However, once it has been layouted, Android crops everything away that was outside at the time of layout-update, so once it starts to animate the bottom part is missing.
So my question is, how can I prevent Android from cropping my ImageView?
I wrote a class that seems to solve the problem, thought I´d share it if someone else faces this problem..
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ImageView;
public class NoCropImageView extends ImageView
{
private int fixedWidth = 0;
private int fixedHeight = 0;
private int[] attrsArray = new int[] { android.R.attr.layout_width, android.R.attr.layout_height };
public NoCropImageView(final Context context, final AttributeSet attrs)
{
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, attrsArray);
int layout_width = ta.getDimensionPixelSize(0, ViewGroup.LayoutParams.MATCH_PARENT);
int layout_height = ta.getDimensionPixelSize(1, ViewGroup.LayoutParams.MATCH_PARENT);
ta.recycle();
fixedWidth = layout_width;
fixedHeight = layout_height;
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
{
this.setMeasuredDimension(fixedWidth, fixedHeight);
}
}
OK, I have a settings activity that is filled from a preferences.xml with addPreferencesFromResource(R.xml.preferences);, there is a dialog preference:
<com.android.maxr1998.cleanit.DialogPreference
android:title="#string/pref_dev"
android:key="dev"
android:dialogIcon="#drawable/alerts_and_states_warning"
android:dialogLayout="#layout/dev_dialog"
android:positiveButtonText="#android:string/yes"
android:negativeButtonText="#android:string/cancel"/>
Now, when I click Yes, I want another Activity to be launched...
This is my DialogPreference.class
package com.android.maxr1998.cleanit;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Region;
import android.provider.ContactsContract;
import android.util.AttributeSet;
public class DialogPreference extends android.preference.DialogPreference {
public DialogPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DialogPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public interface ClickListener {
public void onPositiveBtnClicked(...?);
}
}
What do I specifically have to put at "...?" ?
Solved it this way:
Used normal preference
Applied onclicklistener on Preference in Activity
Launch AlertDialog from it
Positive button click in alertdialog fires up activity
Write a Interface for the DialogPreference. Implement that in your activity. In positive click event callback.
class DialogPreference extends ... {
public interface ClickListener {
public void onPositiveBtnClicked(...);
}
public void setListener(ClickListener l) {
mClickListener = l;
}
#Override
onClick(DialogInterface dialog, int which) {
if(which == DialogInterface .BUTTON_POSITIVE) {
mClickListener.onPositiveBtnClicked(...)
}
}
}